13.2.1. Classes That Act Like Values

Image

To provide valuelike behavior, each object has to have its own copy of the resource that the class manages. That means each HasPtr object must have its own copy of the string to which ps points. To implement valuelike behavior HasPtr needs

• A copy constructor that copies the string, not just the pointer

• A destructor to free the string

• A copy-assignment operator to free the object’s existing string and copy the string from its right-hand operand

The valuelike version of HasPtr is

class HasPtr {
public:
    HasPtr(const std::string &s = std::string()):
        ps(new std::string(s)), i(0) { }
    // each HasPtr has its own copy of the string to which ps points
    HasPtr(const HasPtr &p):
        ps(new std::string(*p.ps)), i(p.i) { }
    HasPtr& operator=(const HasPtr &);
    ~HasPtr() { delete ps; }
private:
    std::string *ps;
    int    i;
};

Our class is simple enough that we’ve defined all but the assignment operator in the class body. The first constructor takes an (optional) string argument. That constructor dynamically allocates its own copy of that string and stores a pointer to that string in ps. The copy constructor also allocates its own, separate copy of the string. The destructor frees the memory allocated in its constructors by executing delete on the pointer member, ps.

Valuelike Copy-Assignment Operator

Assignment operators typically combine the actions of the destructor and the copy constructor. Like the destructor, assignment destroys the left-hand operand’s resources. Like the copy constructor, assignment copies data from the right-hand operand. However, it is crucially important that these actions be done in a sequence that is correct even if an object is assigned to itself. Moreover, when possible, we should also write our assignment operators so that they will leave the left-hand operand in a sensible state should an exception occur (§ 5.6.2, p. 196).

In this case, we can handle self-assignment—and make our code safe should an exception happen—by first copying the right-hand side. After the copy is made, we’ll free the left-hand side and update the pointer to point to the newly allocated string:

HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
    auto newp = new string(*rhs.ps);   // copy the underlying string
    delete ps;       // free the old memory
    ps = newp;       // copy data from rhs into this object
    i = rhs.i;
    return *this;    // return this object
}

In this assignment operator, we quite clearly first do the work of the constructor: The initializer of newp is identical to the initializer of ps in HasPtr’s copy constructor. As in the destructor, we next delete the string to which ps currently points. What remains is to copy the pointer to the newly allocated string and the int value from rhs into this object.

To illustrate the importance of guarding against self-assignment, consider what would happen if we wrote the assignment operator as

// WRONG way to write an assignment operator!
HasPtr&
HasPtr::operator=(const HasPtr &rhs)
{
    delete ps;   // frees the string to which this object points
    // if rhs and *this are the same object, we're copying from deleted memory!
    ps = new string(*(rhs.ps));
    i = rhs.i;
    return *this;
}

If rhs and this object are the same object, deleting ps frees the string to which both *this and rhs point. When we attempt to copy * (rhs.ps) in the new expression, that pointer points to invalid memory. What happens is undefined.


Image Warning

It is crucially important for assignment operators to work correctly, even when an object is assigned to itself. A good way to do so is to copy the right-hand operand before destroying the left-hand operand.



Exercises Section 13.2.1

Exercise 13.23: Compare the copy-control members that you wrote for the solutions to the previous section’s exercises to the code presented here. Be sure you understand the differences, if any, between your code and ours.

Exercise 13.24: What would happen if the version of HasPtr in this section didn’t define a destructor? What if HasPtr didn’t define the copy constructor?

Exercise 13.25: Assume we want to define a version of StrBlob that acts like a value. Also assume that we want to continue to use a shared_ptr so that our StrBlobPtr class can still use a weak_ptr to the vector. Your revised class will need a copy constructor and copy-assignment operator but will not need a destructor. Explain what the copy constructor and copy-assignment operators must do. Explain why the class does not need a destructor.

Exercise 13.26: Write your own version of the StrBlob class described in the previous exercise.


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

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