First, think about the automatic sizing feature. In Listing 16.3, what happens each time the program appends a letter to a string? It can’t necessarily just grow the string in place because it might run into neighboring memory that is already in use. So it may have to allocate a new block and then copy the old contents to a new location. It would be inefficient to do this a lot, so many C++ implementations allocate a block of memory larger than the actual string, giving the string room to grow. Then if the string eventually exceeds that size, the program allocates a new block twice the size to afford more room to grow without continuous resizing. The capacity() method returns the size of the current block, and the reserve() method allows you to request a minimum size for the block. Listing 16.4 shows an example that uses these methods.
Listing 16.4. str2.cpp
// str2.cpp -- capacity() and reserve()
#include
#include
int main()
{
using namespace std;
string empty;
string small = "bit";
string larger = "Elephants are a girl's best friend";
cout << "Sizes:\n";
cout << "\tempty: " << empty.size() << endl;
cout << "\tsmall: " << small.size() << endl;
cout << "\tlarger: " << larger.size() << endl;
cout << "Capacities:\n";
cout << "\tempty: " << empty.capacity() << endl;
cout << "\tsmall: " << small.capacity() << endl;
cout << "\tlarger: " << larger.capacity() << endl;
empty.reserve(50);
cout << "Capacity after empty.reserve(50): "
<< empty.capacity() << endl;
return 0;
}
Here is the output of the program in Listing 16.4 for one C++ implementation:
Sizes:
empty: 0
small: 3
larger: 34
Capacities:
empty: 15
small: 15
larger: 47
Capacity after empty.reserve(50): 63
Note that this implementation uses a minimum capacity of 15 characters and seems to use 1 less than multiples of 16 as standard choices for capacities. Other implementations may make different choices.
What if you have a string object but need a C-style string? For example, you might want to open a file whose name is in a string object:
string filename;
cout << "Enter file name: ";
cin >> filename;
ofstream fout;
The bad news is that the open() method requires a C-style string argument. The good news is that the c_str() method returns a pointer to a C-style string that has the same contents as the invoking string object. So you can use this:
fout.open(filename.c_str());
String Varieties
This section treats the string class as if it were based on the char type. In fact, as mentioned earlier, the string library really is based on a template class:
template
class Allocator = allocator
basic_string {...};
The basic_string template comes with four specializations, each of which has a typedef name:
typedef basic_string
typedef basic_string
typedef basic_string
typedef basic_string
This allows you to use strings based on the wchar_t, char16_t, and char32_t types as well as the char type. You could even develop some sort of character-like class and use the basic_string class template with it, provided that your class met certain requirements. The traits class describes specific facts about the chosen character type, such as how to compare values. There are predefined specializations of the char_traits template for the char, wchar_t, char16_t, and char32_t types, and these are the default values for traits. The Allocator class represents a class to manage memory allocation. There are predefined specializations of the allocator template for the various character types, and these are the defaults. They use new and delete.
Smart Pointer Template Classes
A
void remodel(std::string & str)
{
std::string * ps = new std::string(str);
...
str = ps;
return;
}
You probably see its flaw. Each time the function is called, it allocates memory from the heap but never frees the memory, thus creating a memory leak. You also know the solution—just remember to free the allocated memory by adding the following statement just before the return statement:
delete ps;
However, a solution involving the phrase “just remember to” is seldom the best solution. Sometimes you won’t remember. Or you will remember but accidentally remove or comment out the code. And even if you do remember, there can still be problems. Consider the following variation:
void remodel(std::string & str)
{
std::string * ps = new std::string(str);
...
if (weird_thing())
throw exception();
str = *ps;
delete ps;
return;
}
If the exception is thrown, the delete statement isn’t reached, and again there is a memory leak.