13.1.4. The Rule of Three/Five

Image

As we’ve seen, there are three basic operations to control copies of class objects: the copy constructor, copy-assignment operator, and destructor. Moreover, as we’ll see in § 13.6 (p. 531), under the new standard, a class can also define a move constructor and move-assignment operator.


Exercises Section 13.1.3

Exercise 13.9: What is a destructor? What does the synthesized destructor do? When is a destructor synthesized?

Exercise 13.10: What happens when a StrBlob object is destroyed? What about a StrBlobPtr?

Exercise 13.11: Add a destructor to your HasPtr class from the previous exercises.

Exercise 13.12: How many destructor calls occur in the following code fragment?


bool fcn(const Sales_data *trans, Sales_data accum)
{
    Sales_data item1(*trans), item2(accum);
    return item1.isbn() != item2.isbn();
}

Exercise 13.13: A good way to understand copy-control members and constructors is to define a simple class with these members in which each member prints its name:


struct X {
    X() {std::cout << "X()" << std::endl;}
    X(const X&) {std::cout << "X(const X&)" << std::endl;}
};

Add the copy-assignment operator and destructor to X and write a program using X objects in various ways: Pass them as nonreference and reference parameters; dynamically allocate them; put them in containers; and so forth. Study the output until you are certain you understand when and why each copy-control member is used. As you read the output, remember that the compiler can omit calls to the copy constructor.


There is no requirement that we define all of these operations: We can define one or two of them without having to define all of them. However, ordinarily these operations should be thought of as a unit. In general, it is unusual to need one without needing to define them all.

Classes That Need Destructors Need Copy and Assignment

One rule of thumb to use when you decide whether a class needs to define its own versions of the copy-control members is to decide first whether the class needs a destructor. Often, the need for a destructor is more obvious than the need for the copy constructor or assignment operator. If the class needs a destructor, it almost surely needs a copy constructor and copy-assignment operator as well.

The HasPtr class that we have used in the exercises is a good example (§ 13.1.1, p. 499). That class allocates dynamic memory in its constructor. The synthesized destructor will not delete a data member that is a pointer. Therefore, this class needs to define a destructor to free the memory allocated by its constructor.

What may be less clear—but what our rule of thumb tells us—is that HasPtr also needs a copy constructor and copy-assignment operator.

Consider what would happen if we gave HasPtr a destructor but used the synthesized versions of the copy constructor and copy-assignment operator:

class HasPtr {
public:
    HasPtr(const std::string &s = std::string()):
        ps(new std::string(s)), i(0) { }
    ~HasPtr() { delete ps; }
    // WRONG: HasPtr needs a copy constructor and copy-assignment operator
    // other members as before
};

In this version of the class, the memory allocated in the constructor will be freed when a HasPtr object is destroyed. Unfortunately, we have introduced a serious bug! This version of the class uses the synthesized versions of copy and assignment. Those functions copy the pointer member, meaning that multiple HasPtr objects may be pointing to the same memory:

HasPtr f(HasPtr hp)  // HasPtr passed by value, so it is copied
{
    HasPtr ret = hp; // copies the given HasPtr
    // process ret
    return ret;      // ret and hp are destroyed
}

When f returns, both hp and ret are destroyed and the HasPtr destructor is run on each of these objects. That destructor will delete the pointer member in ret and in hp. But these objects contain the same pointer value. This code will delete that pointer twice, which is an error (§ 12.1.2, p. 462). What happens is undefined.

In addition, the caller of f may still be using the object that was passed to f:

HasPtr p("some values");
f(p);        // when f completes, the memory to which p.ps points is freed
HasPtr q(p); // now both p and q point to invalid memory!

The memory to which p (and q) points is no longer valid. It was returned to the system when hp (or ret!) was destroyed.


Image Tip

If a class needs a destructor, it almost surely also needs the copy-assignment operator and a copy constructor.


Classes That Need Copy Need Assignment, and Vice Versa

Although many classes need to define all of (or none of) the copy-control members, some classes have work that needs to be done to copy or assign objects but has no need for the destructor.

As an example, consider a class that gives each object its own, unique serial number. Such a class would need a copy constructor to generate a new, distinct serial number for the object being created. That constructor would copy all the other data members from the given object. This class would also need its own copy-assignment operator to avoid assigning to the serial number of the left-hand object. However, this class would have no need for a destructor.

This example gives rise to a second rule of thumb: If a class needs a copy constructor, it almost surely needs a copy-assignment operator. And vice versa—if the class needs an assignment operator, it almost surely needs a copy constructor as well. Nevertheless, needing either the copy constructor or the copy-assignment operator does not (necessarily) indicate the need for a destructor.


Exercises Section 13.1.4

Exercise 13.14: Assume that numbered is a class with a default constructor that generates a unique serial number for each object, which is stored in a data member named mysn. Assuming numbered uses the synthesized copy-control members and given the following function:


void f (numbered s) { cout << s.mysn << endl; }

what output does the following code produce?


numbered a, b = a, c = b;
f(a); f(b); f(c);

Exercise 13.15: Assume numbered has a copy constructor that generates a new serial number. Does that change the output of the calls in the previous exercise? If so, why? What output gets generated?

Exercise 13.16: What if the parameter in f were const numbered&? Does that change the output? If so, why? What output gets generated?

Exercise 13.17: Write versions of numbered and f corresponding to the previous three exercises and check whether you correctly predicted the output.


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

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