The problems with the StringBad
class stem from special member functions. These are member functions that are defined automatically. In the case of StringBad
, the behavior of these member functions is inappropriate to this particular class design. In particular, C++ automatically provides the following member functions:
• A default constructor if you define no constructors
• A default destructor if you don’t define one
• A copy constructor if you don’t define one
• An assignment operator if you don’t define one
• An address operator if you don’t define one
More precisely, the compiler generates definitions for the last three items if a program uses objects in such a way as to require them. For example, if you assign one object to another, the program provides a definition for the assignment operator.
It turns out that the implicit copy constructor and the implicit assignment operator cause the StringBad
class problems.
The implicit address operator returns the address of the invoking object (that is, the value of the this
pointer). That’s fine for our purposes, and we won’t discuss this member function further. The default destructor does nothing, and we won’t discuss it, either, other than to point out that the class has already provided a substitute for it. But the others do warrant more discussion.
C++11 provides two more special member functions—the move constructor and the move assignment operator. Chapter 18, “Visiting with the New C++ Standard,” discusses these further.
If you fail to provide any constructors at all, C++ provides you with a default constructor. For example, suppose you define a Klunk
class and omit any constructors. In this case, the compiler supplies the following default:
Klunk::Klunk() { } // implicit default constructor
That is, it supplies a constructor (the defaulted default constructor) that takes no arguments and that does nothing. It’s needed because creating an object always invokes a constructor:
Klunk lunk; // invokes default constructor
The default constructor makes lunk
like an ordinary automatic variable; that is, its value at initialization is unknown.
After you define any constructor, C++ doesn’t bother to define a default constructor. If you want to create objects that aren’t initialized explicitly, you then have to define a default constructor explicitly. It’s a constructor with no arguments, but you can use it to set particular values:
Klunk::Klunk() // explicit default constructor
{
klunk_ct = 0;
...
}
A constructor with arguments still can be a default constructor if all its arguments have default values. For example, the Klunk
class could have the following inline constructor:
Klunk(int n = 0) { klunk_ct = n; }
However, you can have only one default constructor. That is, you can’t do this:
Klunk() { klunk_ct = 0 } // constructor #1
Klunk(int n = 0) { klunk_ct = n; } // ambiguous constructor #2
Why is this ambiguous? Consider the following two declarations:
Klunk kar(10); // clearly matches Klunt(int n)
Klunk bus; // could match either constructor
The second declaration matches constructor #1 (no argument), but it also matches constructor #2 (using the default argument 0
). This will cause the compiler to issue an error message.
A copy constructor is used to copy an object to a newly created object. That is, it’s used during initialization, including passing function arguments by value and not during ordinary assignment. A copy constructor for a class normally has this prototype:
Class_name(const Class_name &);
Note that it takes a constant reference to a class object as its argument. For example, a copy constructor for the String
class would have this prototype:
StringBad(const StringBad &);
You must know two things about a copy constructor: when it’s used and what it does.
A copy constructor is invoked whenever a new object is created and initialized to an existing object of the same kind. This happens in several situations. The most obvious situation is when you explicitly initialize a new object to an existing object. For example, given that motto
is a StringBad
object, the following four defining declarations invoke a copy constructor:
StringBad ditto(motto); // calls StringBad(const StringBad &)
StringBad metoo = motto; // calls StringBad(const StringBad &)
StringBad also = StringBad(motto);
// calls StringBad(const StringBad &)
StringBad * pStringBad = new StringBad(motto);
// calls StringBad(const StringBad &)
Depending on the implementation, the middle two declarations may use a copy constructor directly to create metoo
and also
, or they may use a copy constructor to generate temporary objects whose contents are then assigned to metoo
and also
. The last example initializes an anonymous object to motto
and assigns the address of the new object to the pstring
pointer.
Less obviously, a compiler uses a copy constructor whenever a program generates copies of an object. In particular, it’s used when a function passes an object by value (as callme2()
does in Listing 12.3) or when a function returns an object. Remember, passing by value means creating a copy of the original variable. A compiler also uses a copy constructor whenever it generates temporary objects. For example, a compiler might generate a temporary Vector
object to hold an intermediate result when adding three Vector
objects. Compilers vary as to when they generate temporary objects, but all invoke a copy constructor when passing objects by value and when returning them. In particular, this function call in Listing 12.3 invokes a copy constructor:
callme2(headline2);
The program uses a copy constructor to initialize sb
, the formal StringBad
-type parameter for the callme2()
function.
By the way, the fact that passing an object by value involves invoking a copy constructor is a good reason for passing by reference instead. That saves the time of invoking the constructor and the space for storing the new object.
The default copy constructor performs a member-by-member copy of the nonstatic members (memberwise copying, also sometimes called shallow copying). Each member is copied by value. In Listing 12.3, the statement
StringBad sailor = sports;
amounts to the following (aside from the fact that it doesn’t compile because access to private members is not allowed):
StringBad sailor;
sailor.str = sports.str;
sailor.len = sports.len;
If a member is itself a class object, the copy constructor for that class is used to copy one member object to another. Static members, such as num_strings
, are unaffected because they belong to the class as a whole instead of to individual objects. Figure 12.2 illustrates the action of an implicit copy constructor.
18.190.217.253