Often it is much better to decide many matters, such as how much storage to use, when a program runs rather than when it’s compiled. The usual C++ approach to storing a name in an object is to use the new operator in a class constructor to allocate the correct amount of memory while the program is running. The usual way to accomplish this is to use the string class, which takes care of the memory management details for you. But you won’t learn much about memory management that way, so let’s attack the problem directly. Introducing new to a class constructor raises several new problems unless you remember to take a series of additional steps, such as expanding the class destructor, bringing all constructors into harmony with the new destructor, and writing additional class methods to facilitate correct initialization and assignment. (This chapter, of course, explains all these steps.)
Dynamic Memory and Classes
What would you like for breakfast, lunch, and dinner for the next month? How many ounces of milk for dinner on the 3rd day? How many raisins in your cereal for breakfast on the 15th day? If you’re like most people, you’d rather postpone some of those decisions until the actual mealtimes. Part of the strategy in C++ is to take the same attitude toward memory allocation, letting the program decide about memory during runtime rather than during compile time. That way, memory use can depend on the needs of a program instead of on a rigid set of storage-class rules. Remember that to gain dynamic control of memory, C++ utilizes the new and delete operators. Unhappily, using these operators with classes can pose some new programming problems. As you’ll see, destructors can become necessary instead of merely ornamental. And sometimes you have to overload an assignment operator to get a program to behave properly. We’ll look into these matters now.
A Review Example and Static Class Members
We haven’t used new and delete for a while, so let’s review them with a short program. While we’re at it, let’s look at a new storage class: the static class member. The vehicle will be a StringBad class, later to be superseded by the slightly more able String class. (You’ve already seen the standard C++ string class, and you’ll learn more about it in Chapter 16, “The string Class and the Standard Template Library.” Meanwhile, the humble StringBad and String classes in this chapter provide some insight into what underlies such a class. A lot of programming techniques go into providing such a friendly interface.)
StringBad and String class objects will hold a pointer to a string and a value representing the string length. We’ll use the StringBad and String classes primarily to give an inside look at how new, delete, and static class members operate. For that reason, the constructors and destructors will display messages when called so that you can follow the action. Also we’ll omit several useful member and friend functions, such as overloaded ++ and >> operators and a conversion function, in order to simplify the class interface. (But rejoice! The review questions for this chapter give you the opportunity to add those useful support functions.) Listing 12.1 shows the class declaration.
Listing 12.1. strngbad.h
// strngbad.h -- flawed string class definition
#include
#ifndef STRNGBAD_H_
#define STRNGBAD_H_
class StringBad
{
private:
char * str; // pointer to string
int len; // length of string
static int num_strings; // number of objects
public:
StringBad(const char * s); // constructor
StringBad(); // default constructor
~StringBad(); // destructor
// friend function
friend std::ostream & operator<<(std::ostream & os,
const StringBad & st);
};
#endif
Why call the class StringBad? This is to remind you that StringBad is an example under development. It’s the first stage of developing a class by using dynamic memory allocation, and it does the obvious things correctly; for example, it uses new and delete correctly in the constructors and destructor. It doesn’t really do bad things, but the design omits doing some additional good things that are necessary but not at all obvious. Seeing the problems the class has should help you understand and remember the non-obvious changes you will make later, when you convert it to the more functional String class.
You should note two points about this declaration. First, it uses a pointer-to-char instead of a char array to represent a name. This means that the class declaration does not allocate storage space for the string itself. Instead, it uses new in the constructors to allocate space for the string. This arrangement avoids straitjacketing the class declaration with a predefined limit to the string size.