Section 13.1. The Copy Constructor


13.1. The Copy Constructor

The constructor that takes a single parameter that is a (usually const) reference to an object of the class type itself is called the copy constructor. Like the default constructor, the copy constructor can be implicitly invoked by the compiler. The copy constructor is used to

  • Explicitly or implicitly initialize one object from another of the same type

  • Copy an object to pass it as an argument to a function

  • Copy an object to return it from a function

  • Initialize the elements in a sequential container

  • Initialize elements in an array from a list of element initializers

Forms of Object Definition

Recall that C++ supports two forms of initialization (Section 2.3.3, p. 48): direct and copy. Copy-initialization uses the = symbol, and direct-initialization places the initializer in parentheses.

The copy and direct forms of initialization, when applied to objects of class type, are subtly different. Direct-initialization directly invokes the constructor matched by the arguments. Copy-initialization always involves the copy constructor. Copy-initialization first uses the indicated constructor to create a temporary object (Section 7.3.2, p. 247). It then uses the copy constructor to copy that temporary into the one we are creating:

      string null_book = "9-999-99999-9"; // copy-initialization      string dots(10, '.');               // direct-initialization      string empty_copy = string();       // copy-initialization      string empty_direct;                // direct-initialization 

For objects of class type, copy-initialization can be used only when specifying a single argument or when we explicitly build a temporary object to copy.

When dots is created, the string constructor that takes a count and a character is called and directly initializes the members in dots. To create null_book, the compiler first creates a temporary by invoking the string constructor that takes a C-style character string parameter. The compiler then uses the string copy constructor to initialize null_book as a copy of that temporary.

The initialization of empty_copy and empty_direct both call the string default constructor. In the first case, the default constructor creates a temporary object, which is then used by the copy constructor to initialize empty_copy. In the second case, the default constructor is run directly on empty_direct.

The copy form of initialization is primarily supported for compatibility with C usage. When it can do so, the compiler is permitted (but not obligated) to skip the copy constructor and create the object directly.

Usually the difference between direct- or copy-initialization is at most a matter of low-level optimization. However, for types that do not support copying, or when using a constructor that is nonexplicit (Section 12.4.4, p. 462) the distinction can be essential:

      ifstream file1("filename"); // ok: direct initialization      ifstream file2 = "filename"; // error: copy constructor is private      // This initialization is okay only if      // the Sales_item(const string&) constructor is not explicit      Sales_item item = string("9-999-99999-9"); 

The initialization of file1 is fine. The ifstream class defines a constructor that can be called with a C-style string. That constructor is used to initialize file1.

The seemingly equivalent initialization of file2 uses copy-initialization. That definition is not okay. We cannot copy objects of the IO types (Section 8.1, p. 287), so we cannot use copy-initialization on objects of these types.

Whether the initialization of item is okay depends on which version of our Sales_item class we are using. Some versions define the constructor that takes a string as explicit. If the constructor is explicit, then the initialization fails. If the constructor is not explicit, then the initialization is fine.

Parameters and Return Values

As we know, when a parameter is a nonreference type (Section 7.2.1, p. 230), the argument is copied. Similarly, a nonreference return value (Section 7.3.2, p. 247) is returned by copying the value in the return statement.

When the parameter or return type is a class type, the copy is done by the copy constructor. For example, consider our make_plural function from page 248:

      // copy constructor used to copy the return value;      // parameters are references, so they aren't copied      string make_plural(size_t, const string&, const string&); 

This function implicitly uses the string copy constructor to return the plural version of a given word. The parameters are const references; they are not copied.

Initializing Container Elements

The copy constructor is used to initialize the elements in a sequential container. For example, we can initialize a container using a single parameter that represents a size (Section 3.3.1, p. 92). This form of construction uses both the default constructor and the copy constructor for the element container:

      // default string constructor and five string copy constructors invoked      vector<string> svec(5); 

The compiler initializes svec by first using the default string constructor to create a temporary value. The copy constructor is then used to copy the temporary into each element of svec.

As a general rule (Section 9.1.1, p. 307), unless you intend to use the default initial value of the container elements, it is more efficient to allocate an empty container and add elements as the values for those elements become known.



Constructors and Array Elements

If we provide no element initializers for an array of class type, then the default constructor is used to initialize each element. However, if we provide explicit element initializers using the normal brace-enclosed array initialization list (Section 4.1.1, p. 111), then each element is initialized using copy-initialization. An element of the appropriate type is created from the specified value, and then the copy constructor is used to copy that value to the corresponding element:

      Sales_item primer_eds[] = { string("0-201-16487-6"),                                  string("0-201-54848-8"),                                  string("0-201-82470-1"),                                  Sales_item()                                }; 

A value that can be used to invoke a single-argument constructor for the element type can be specified directly, as in the initializers for the first three elements. If we wish to specify no arguments or multiple arguments, we need to use the full constructor syntax, as we do in the initializer for the last element.

Exercises Section 13.1

Exercise 13.1:

What is a copy constructor? When is it used?

Exercise 13.2:

The second initialization below fails to compile. What can we infer about the definition of vector?

[View full width]

vector<int> v1(42); // ok: 42 elements, each 0 vector<int> v2 = 42; // error: what does this error tell us about vector?

Exercise 13.3:

Assuming Point is a class type with a public copy constructor, identify each use of the copy constructor in this program fragment:

      Point global;      Point foo_bar(Point arg)      {           Point local = arg;           Point *heap = new Point(global);           *heap = local;           Point pa[ 4 ] = { local, *heap };           return *heap;      } 


13.1.1. The Synthesized Copy Constructor

If we do not otherwise define the copy constructor, the compiler synthesizes one for us. Unlike the synthesized default constructor (Section 12.4.3, p. 458), a copy constructor is synthesized even if we define other constructors. The behavior of the synthesized copy constructor is to memberwise initialize the new object as a copy of the original object.

By memberwise, we mean that taking each nonstatic member in turn, the compiler copies the member from the existing object into the one being created. With one exception, the type of each member determines what it means to copy it. The synthesized copy constructor directly copies the value of members of built-in type. Members of class type are copied by using the copy constructor for that class. The one exception concerns array members. Even though we ordinarily cannot copy an array, if a class has a member that is an array, then the synthesized copy constructor will copy the array. It does so by copying each element.

The simplest conceptual model of memberwise initialization is to think of the synthesized copy constructor as one in which each data member is initialized in the constructor initializer list. For example, given our Sales_item class, which has three data members

      class Sales_item {      // other members and constructors as before      private:          std::string isbn;          int units_sold;          double revenue;      }; 

the synthesized Sales_item copy constructor would look something like:

      Sales_item::Sales_item(const Sales_item &orig):          isbn(orig.isbn),              // uses string copy constructor          units_sold(orig.units_sold),  // copies orig.units_sold          revenue(orig.revenue)         // copy orig.revenue          {    }                        // empty body 

13.1.2. Defining Our Own Copy Constructor

The copy constructor is the constructor that takes a single parameter that is a (usually const) reference to the class type:

      class Foo {      public:         Foo();           // default constructor         Foo(const Foo&); // copy constructor         // ...      }; 

Usually the parameter is a const reference, although we can also define the copy constructor to take a nonconst reference. Because the constructor is used (implicitly) to pass and return objects to and from functions, it usually should not be made explicit (Section 12.4.4, p. 462). The copy constructor should copy the members from its argument into the object that is being constructed.

For many classes, the synthesized copy constructor does exactly the work that is needed. Classes that contain only members that are of class type or members that are of built-in (but not pointer type) often can be copied without explicitly defining the copy constructor.

However, some classes must take control of what happens when objects are copied. Such classes often have a data member that is a pointer or that represents another resource that is allocated in the constructor. Other classes have bookkeeping that must be done whenever a new object is created. In both these cases, the copy constructor must be defined.

Often the hardest part about defining a copy constructor is recognizing that a copy constructor is needed. Defining the constructor is usually pretty easy once the need for the constructor is recognized. The copy constructor itself is defined like any other constructor: It has the same name as the name of the class, it has no return value, it may (should) use a constructor initializer to initialize the members of the newly created object, and it may do any other necessary work inside a function body.

We'll look at examples of classes that require class-defined copy constructors in later sections. Section 13.4 (p. 486) looks at a pair of classes that require an explicit copy constructor to handle bookkeeping associated with a simple message-handling application; classes with members that are pointers are covered in Section 13.5 (p. 492).

Exercises Section 13.1.2

Exercise 13.4:

Given the following sketch of a class, write a copy constructor that copies all the elements. Copy the object to which pstring points, not the pointer.

      struct NoName {          NoName(): pstring(new std::string), i(0), d(0) { }      private:          std::string *pstring;          int    i;          double d;      }; 

Exercise 13.5:

Which class definition is likely to need a copy constructor?

  1. A Point3w class containing four float members

  2. A Matrix class in which the actual matrix is allocated dynamically within the constructor and is deleted within its destructor

  3. A Payroll class in which each object is provided with a unique ID

  4. A Word class containing a string and a vector of line and column location pairs

Exercise 13.6:

The parameter of the copy constructor does not strictly need to be const, but it does need to be a reference. Explain the rationale for this restriction. For example, explain why the following definition could not work.

      Sales_item::Sales_item(const Sales_item rhs); 


13.1.3. Preventing Copies

Some classes need to prevent copies from being made at all. For example, the iostream classes do not permit copying (Section 8.1, p. 287). It might seem that if we want to forbid copies, we could omit the copy constructor. However, if we don't define a copy constructor, the compiler will synthesize one.

To prevent copies, a class must explicitly declare its copy constructor as private.



If the copy constructor is private, then user code will not be allowed to copy objects of the class type. The compiler will reject any attempt to make a copy.

However, the friends and members of the class could still make copies. If we want to prevent copies even within the friends and members, we can do so by declaring a (private) copy constructor but not defining it.

It is legal to declare but not define a member function. However, any attempt to use an undefined member results in a link-time failure. By declaring (but not defining) a private copy constructor, we can forestall any attempt to copy an object of the class type: Attempts to make copies in user code will be flagged as an error at compile time, and attempts to make copies in member functions or friends will result in an error at link time.

Most Classes Should Define Copy and Default Constructors

Classes that do not define the default constructor and/or the copy constructor impose serious limits on users of the class. Objects of classes that do not allow copies may be passed to (or returned from) a function only as a reference. They also may not be used as elements in a container.

It is usually best to defineeither implicitly or explicitlythe default and copy constructors. The default constructor is synthesized only if there are no other constructors. If the copy constructor is defined, then the default constructor must be defined as well.





C++ Primer
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2006
Pages: 223
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net