Special Member Functions

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.

Default Constructors

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.

Copy Constructors

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.

When a Copy Constructor Is Used

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.

What a Default Copy Constructor Does

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.

Figure 12.2. An inside look at memberwise copying.

Image

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.190.217.253