Ordinarily classes that manage resources that do not reside in the class must define the copy-control members. Once a class needs a destructor, it almost surely needs a copy constructor and copy-assignment operator as well.
In order to define these members, we first have to decide what copying an object of our type will mean. In general, we have two choices: we can define the copy operations to make the class behave like a value or like a pointer.
1.Classes That Act Like Values
1 #include <string> 2 using std::string; 3 4 class Has_ptr 5 { 6 public: 7 // constructor with default argument 8 Has_ptr(const std::string &s = std::string()) : 9 ps(new std::string(s)), i(0) { } 10 // copy destructor: 11 //each Has_ptr has its own copy of the string to which ps points 12 Has_ptr(const Has_ptr &p) : 13 ps(new std::string(*p.ps)), i(p.i) { } 14 // assignment operator 15 Has_ptr &operator=(const Has_ptr &rhs); 16 // destructor: free the memory allocated in constructor 17 ~Has_ptr() { delete ps; } 18 private: 19 std::string *ps; 20 int i; 21 }; 22 23 // assignment operator: can handle self-assignment 24 Has_ptr &Has_ptr::operator=(const Has_ptr &rhs) 25 { 26 string *newp = new string(*rhs.ps); // copy the underlying string 27 delete ps; // free the old memory 28 ps = newp; // copy data from rhs into this object 29 i = rhs.i; 30 return *this; 31 }
There are two points to keep in mind when you write an assignment operator:
- Assignment operators must work correctly if an object is assigned to itself.
- Most assignment operators share work with the destructor and copy constructor.
A good pattern to use when you write an assignment operator is to first copy the right-hand operand into a local temporary. After the copy is done, it is safe to destroy the existing members of the left-hand operand. Once the left-hand operand is destroyed, copy the data from the temporary into the members of the left-hand operand.
What if Has_ptr didn‘t define the copy constructor(Exercise 13.24)?
We‘ll use the synthesized version of the copy constructor. In this case, we have introduced a serious bug! Synthesized copy constructor copy the pointer member, meaning that multiple Has_ptr objects may be pointing to the same memory.
2.Defining classes That Act Like Pointers
In this case, though, the destructor of Has_ptr cannot unilaterally free its associated string. It can do so only when the last Has_ptr pointing to that string goes away.
When we copy or assign a Has_ptr object, we want the copy and the original to point to the same string.
1 #include <string> 2 using std::string; 3 4 class Has_ptr 5 { 6 public: 7 // constructor allocates a new string and a new counter, which it sets to 1 8 Has_ptr(const std::string &s = std::string()) : 9 ps(new std::string(s)), i(0), use(new std::size_t(1)) { } 10 // copy constructor copies all three data member and increment the counter 11 Has_ptr(const Has_ptr &p) : 12 ps(p.ps), i(p.i), use(p.use) { ++*use; } 13 Has_ptr &operator=(const Has_ptr &rhs); 14 ~Has_ptr(); 15 private: 16 std::string *ps; 17 int i; 18 std::size_t *use; // member to keep track of how many objects share *ps 19 }; 20 21 Has_ptr::~Has_ptr() 22 { 23 if (--*use == 0) // if the reference count goes to 0 24 { 25 delete ps; // delete the string 26 delete use; // delete the counter 27 } 28 } 29 30 // the operator must handle self-assignment. 31 // we do so by increment the count in rhs before 32 // decrementing the count in the left-hand object 33 Has_ptr &Has_ptr::operator=(const Has_ptr &rhs) 34 { 35 ++*rhs.use; // increment the use count of the right-hand operand 36 if (--*use == 0) 37 { // then decrement this object‘s counter 38 delete ps; // if no other users 39 delete use; // free this object‘s allocated members 40 } 41 ps = rhs.ps; // copy data from rhs into this object 42 i = rhs.i; 43 use = rhs.use; 44 return *this; // return this object 45 }