!!!Chapter 13 Copy Control

来源:互联网 发布:知其所以然论坛网址 编辑:程序博客网 时间:2024/06/14 06:19

When we define a new type, we specify what happens when objects of that type are copied, assigned, and destroyed. We do so by defining special members: the copy constructor, the assignment operator and the destructor.

If we do no explicitly define the copy constructor or the assignment operator, the compiler will define them for us.

Collectively, the copy constructor, assignment operator, and destructor are referred to as copy control. The compiler automatically implements these operations, but the class may define its own versions.

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.

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

C++ supports two forms of initialization: direct and copy. Copy-initialization uses the = symbol, and direct-initialization places the initializer in parentheses.

For class, 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. It then uses the copy constructor to copy that temporary into the one we are creating:

string null_book = "9-999-9999";     //copystring dots(10, ',');                //directstring empty_copy = string();        //copystring empty_direct;                 //direct

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

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 isnonexplicit the distinction can be essential: P 477

Parameters and Return Values

When the parameter/return value is a nonreference type, it is copied.

Initializing Container Elements

The copy constructor is used to initialize the elements in a sequential container. E.G.

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

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. But if we provide explicit element initializers using the normal brace-enclosed array initialization list, then each element is initialized using copy-initialization. P 479.

13.1.1 The Synthesized Copy Constructor

The compiler will synthesize copy constructor for us, if we do not define the copy constructor.

The behavior of the synthesized copy constructor is memberwise initialize the new object as a copy of the original object.

memberwise means taking each nonstatic member in turn, the compiler copies the member from the existing object into the one being created:

  • For built-in type, the value of members are copied directly
  • For members of class type, it will use copy constructor for that class
  • For array members, copy constructor will copy the array by copying each element. (normally, we cannot copy an array)

13.1.2 Defining Our Own Copy Constructor

The copy constructor 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.

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

Because the constructor is used(implicitly) to pass and return objects to and from functions, it usually should not be made explicit.
For classes that contain only members of class type or built-in type, we can rely on synthesized copy constructor.

For classes that have a data member of pointer or do bookkeeping, we may need to define our own copy constructor.

13.1.3 Preventing Copies

To prevent copies, we can define the copy constructor as private. In this way, only friends and members of the class can make copies.

To prevent copies within friends and members, we can do so by declaring a (private) copy constructor but not defining it.

Objects of classes that do not allow copies may be passed to(return from) a function only as a reference. They also may not be used as elements in a container.

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.

13.2 The Assignment Operator

As with the copy constructor, the compiler synthesizes an assignment operator if the class does not define its own.

Introducing Overloaded Assignment

Overloaded operators are functions that have the name operator followed by the symbol for the operator being defined. E.G. operator=

An operator function also has a return type and a parameter list. The parameter list must have the same number of parameters as the operator has operands.

Most operators may be defined as member or nonmember functions. When an operator is a member function, its first operand is implicitly bound to thethispointer.

Assignment function should return a reference to its left-hand operand.

class Sales_item{public:    Sales_item& operator= (const Sales_item &);    //declaration so need a ;};

The Synthesized Assignment Operator

The synthesized assignment operator operates similarly to the synthesized copy constructor. It performsmemberwise assignment.

Sales_item&Sales_item::operator= (const Sales_item &rhs){    isbn = rhs.isbn;    units_sold = rhs.units_sold;    revenue = rhs.revenue;    return *this;}

The operator returns *this, which is a reference to the left-hand object. (有返回值,所以可以连等: a=b=c)

Copy and Assign Usually Go Together

Classes that can use the synthesized copy constructor usually can use the synthesized assignment operator as well.

If a class needs a copy constructor, it will also need an assignment operator.

13.3 The Destructor

The destructor serves as the complement to the constructors of the class.

When a Destructor Is Called

The destructor is called automatically whenever an object of its class is destroyed:

// p points to default constructed objectSales_item *p = new Sales_item;{                             //new scope    Sales_item item(*p);      //copy constructor copies *p into item    delete p;                 //destructor called on object pointed to by p}                             //exit local scope; destructor called on item
Variable such as item are destroyed automatically when they go out of scope. Hence, the destructor on item is run when the close curly is encountered.

An object that is dynamically allocated is destroyed only when a pointer pointing to the object is deleted.

The destructor is not run when a reference or a pointer to an object goes out of scope. The destructor is run only when a pointer to a dynamically allocated object is deleted or when an actual object goes out of scope.

When to Write an Explicit Destructor

Rule of Three: if a class needs a destructor, it will also need the assignment operator and a copy constructor.

The Synthesized Destructor

Unlike the copy constructor or assignment operator, the compiler always synthesizes a destructor for us.The synthesized destructor destroys nonstatic member in reverse order.

How to Write a Destructor

Classes that do allocate resources usually need to define a destructor to free those resources.

The destructor is a member function with the name of the class prefixed by a tilde (~). It has no return value and takes no parameters. Since it has no parameter, it cannot be overloaded.

Even if we write our own destructor, the synthesized destructor is still run.

class Sales_item {public:  ~Sales_item() {}    //define a destructor};

13.5 Managing Pointer Members

classes that contain pointers require careful attention to copy control. The reason they must do so is that copying a pointer copies only the address in the pointer. Copying a pointer does not copy the object to which the pointer points.

Through different copy-control strategies we can implement different behavior for pointer members:

1. The pointer member can be given normal pointerlike behavior: Such classes will have all the pitfalls of pointers but will require no special copy control.
2. The class can implement so-called "smart pointer" behavior. The object to which the pointer points is shared, but the class prevents dangling pointers.

3. The class can be given valuelike behavior. The object to which the pointer points will be unique to and managed separately by each class object.

Normal pointer member P 493

13.5.1 Defining Smart Pointer Classes P 495

A smart pointer behaves like an ordinary pointer except that it adds functionality.

Introducing Use Counts

The use count keeps track of how many objects of the class share the same pointer. When the use count goes to zero, then the object is deleted.

A use count is sometimes also referred to as a reference count.  

The counter cannot go directly into the HasPtr object. P 495

The Use-Count Class

// private class for use by HasPtr onlyclass U_Ptr {    friend class HasPtr;    int *ip;    size_t use;    U_Ptr(int *p) : ip(p), use(1) {}    ~U_Ptr() {delete ip;}};

Using the Use-Counted Class

class HasPtr {public:    HasPtr(int *p, int i) : ptr(new U_Ptr(p)), val(i) {}    HasPtr(const HasPtr &orig) : ptr(orig.ptr), val(orig.val) {++ptr->use;}    HasPtr& operator=(const HasPtr&);    ~HasPtr() { if (--ptr->use==0) delete ptr; }private:    U_Ptr *ptr;    int val;};

Assignment and Use Counts

HasPtr& HasPtr::operator=(const HasPtr &rhs){    ++rhs.ptr->use;    if(--ptr->use == 0)   //anyway, minus one        delete ptr;    ptr = rhs.ptr;    val = rhs.val;    return *this;}
We do increment before decrement, so that we can do self-assignment! (Otherwise, the object may be destroyed!)

13.5.2 Defining Valuelike Classes

When we copy a valuelike object, we get a new, distinct copy:

class HasPtr {public:    HasPtr (const int &p, int i) : ptr(new int(p)), val(i) {}    HasPtr (const HasPtr &orig) : ptr(new int (*orig.ptr)), val(orig.val) {}    HasPtr& operator=(const HasPtr &);    ~HasPtr() {delete ptr;}private:    int *ptr;    int val;};

The copy constructor will allocates a new int object and initialize that object to hold the same value as the object of which it is a copy

The assignment operator doesn't need to allocate a new object. It just has to remember to assign a new value to the object to which its int pointer points:

HasPtr & HasPtr::operator=(const HasPtr &rhs){    *ptr=*rhs.ptr;     val=rhs.val;    return *this;}

The assignment operator must be correct even if we're assigning an object to itself.