Default copy-constructor
Because the copy-constructor implements
pass and return by value, it’s important that the compiler creates one for
you in the case of simple structures – effectively, the same thing it does
in C. However, all you’ve seen so far is the default primitive behavior: a
bitcopy.
When more complex types are involved, the
C++ compiler will still automatically create a copy-constructor if you
don’t make one. Again, however, a bitcopy
doesn’t make sense, because it doesn’t
necessarily implement the proper meaning.
Here’s an example to show the more
intelligent approach the compiler takes. Suppose you create a new class composed
of objects of several existing classes. This is called, appropriately enough,
composition,
and it’s one of the ways you can make new classes from existing classes.
Now take the role of a naive user who’s trying to solve a problem quickly
by creating a new class this way. You don’t know about copy-constructors,
so you don’t create one. The example demonstrates what the compiler does
while creating the default copy-constructor for your new class:
//: C11:DefaultCopyConstructor.cpp
// Automatic creation of the copy-constructor
#include <iostream>
#include <string>
using namespace std;
class WithCC { // With copy-constructor
public:
// Explicit default constructor required:
WithCC() {}
WithCC(const WithCC&) {
cout << "WithCC(WithCC&)" << endl;
}
};
class WoCC { // Without copy-constructor
string id;
public:
WoCC(const string& ident = "") : id(ident) {}
void print(const string& msg = "") const {
if(msg.size() != 0) cout << msg << ": ";
cout << id << endl;
}
};
class Composite {
WithCC withcc; // Embedded objects
WoCC wocc;
public:
Composite() : wocc("Composite()") {}
void print(const string& msg = "") const {
wocc.print(msg);
}
};
int main() {
Composite c;
c.print("Contents of c");
cout << "Calling Composite copy-constructor"
<< endl;
Composite c2 = c; // Calls copy-constructor
c2.print("Contents of c2");
} ///:~
The class WithCC contains a
copy-constructor, which simply announces that it has been called, and this
brings up an interesting issue. In the class Composite, an object of
WithCC is created using a default constructor. If there were no
constructors at all in WithCC, the compiler would automatically create a
default constructor, which would
do nothing in this case. However, if you add a copy-constructor, you’ve
told the compiler you’re going to handle constructor creation, so it no
longer creates a default constructor for you and will complain unless you
explicitly create a default constructor as was done for
WithCC.
The class WoCC has no
copy-constructor, but its constructor will store a message in an internal
string that can be printed out using print( ). This
constructor is explicitly called in Composite’s constructor
initializer list (briefly introduced in Chapter 8 and covered fully in Chapter
14). The reason for this becomes apparent later.
The class Composite has member
objects of both WithCC and WoCC (note the embedded object
wocc is initialized in the constructor-initializer list, as it must be),
and no explicitly defined copy-constructor. However, in main( ) an
object is created using the copy-constructor in the definition:
Composite c2 = c;
The copy-constructor for Composite
is created automatically by the compiler, and the output of the program
reveals the way that it is created:
Contents of c: Composite()
Calling Composite copy-constructor
WithCC(WithCC&)
Contents of c2: Composite()
To create a copy-constructor for a class
that uses composition (and
inheritance,
which is introduced in Chapter 14), the compiler recursively calls the
copy-constructors for all the member objects and base classes. That is, if the
member object also contains another object, its copy-constructor is also called.
So in this case, the compiler calls the copy-constructor for WithCC. The
output shows this constructor being called. Because WoCC has no
copy-constructor, the compiler creates one for it that just performs a bitcopy,
and calls that inside the Composite copy-constructor. The call to
Composite::print( ) in main shows that this happens because the
contents of c2.wocc are identical to the contents of c.wocc. The
process the compiler goes through to synthesize a copy-constructor is called
memberwise
initialization.
It’s always best to create your own
copy-constructor instead of letting the compiler do it for you. This guarantees
that it will be under your
control.