Copy control and resource management
Typically, a class that manages resources outside the class must define copy control members, which require the destructor to release the resources allocated by the object. Once a class requires a destructor, it almost certainly also needs a copy constructor and a copy assignment operator.
In order to define these members, we must first determine the copy semantics of an object of this type. Generally speaking, there are two options, which can define copy operations so that the class behaves like a value or like a pointer.
A class behaves like a value, meaning it should also have its own state. When we copy an object with a class value, the copy and the original object are completely independent. Changing the copy will not have any effect on the original object, and vice versa.
Classes that behave like pointers share state. When we copy an object of this kind, the copy and the original object use the same underlying data. Changing the copy also changes the original object and vice versa.
In the standard library classes we have used, the standard library container and string classes behave like a value. Not surprisingly, the shared_ptr class provides pointer-like behavior, just like our StrBlob class, the IO type and unique_ptr do not allow copying or assignment, so they behave neither like values nor pointers.
To illustrate both ways, we will define copy control members for the HasPtr class in the exercise. First, we will make the class behave like a value; then reimplement the class so that it behaves like a pointer.
Our HasPtr class has two members, an int and a string pointer. Typically, classes directly copy built-in type (excluding pointers) members; these members are values themselves, so they should usually behave like values. How we copy pointer members determines whether a class like HasPtr has class value behavior or class pointer behavior.
Classes with behavioral values
In order to provide class value behavior, each object should have its own copy for class-managed resources. This means that for a string pointed to by ps, each HasPtr object must have its own copy. In order to implement class value behavior, HasPtr requires
- Define a copy constructor to complete the copy of string, rather than copy pointer
- Define a destructor to release string
- Define a copy assignment operator to release the current string of the object and copy the string from the right side of the object
The HasPtr of the class value version is as follows
class HasPtr{ public: HasPtr(const std::string &s=std::string()): ps(new std::string(s)1),i(0){} //For the string pointed to by ps, each BasPtr object has its own copy HasPt(const HasPtr&p): ps(new std::string(*)),i(){} HasPtr & operator=(const HasPtr&); ~HasPtr(){delete ps;} private: std::string*ps; int i; }
Our class is simple enough to define all member functions except assignment operators within the class. The first constructor accepts a (optional) string parameter. This constructor dynamically allocates its own copy of string and saves a pointer to string in ps. The copy constructor also allocates its own string copy. The destructor executes delete on the pointer member ps, freeing the allocated memory in the constructor.
Class value copy assignment operator
Assignment operators usually combine operations of destructors and constructors. Similar to destructors, assignment operations will destroy the resources of the left-hand operation object. Similar to a copy constructor, the assignment operation will copy data from the right-hand operation object. However, it is very important that these operations are performed in the correct order, and even if an object is assigned to itself, it is guaranteed to be correct. Moreover, if possible, the assignment operator we write should also be exceptionally safe---when an exception occurs, it can put the left-hand operation object in a meaningful state.
In this example, by first copying the right-hand operation object, we can handle the self-assignment situation and ensure that the code is also safe when an exception occurs. After the copy is completed, we release the resource of the operation object on the left and update the pointer to the newly allocated string:
HasPtr&HasPtr::operator=(const HasPtr&rhs) { auto newp=new string(*);//Refers to the bottom layer stringdelete ps;//Release old memoryps = newp;//The calculation object from the right refers to the Bei data to this objecti = rhs; return*this;//Return to this object}
In this assignment operator, it is very clear that we first do the constructor work: the initializer of newp is equivalent to the initializer of ps in the copy constructor of HasPtr. Next, like the destructor, we delete the string that ps is currently pointing to. Then only the copy pointer to the newly allocated string is left, and the copying of the int value from rhs to this object.
Key concept: assignment operation
When you write assignment operators: There are two things to remember:
- If an object is assigned to itself, the thief value operation must work correctly.
- The majority assignment operator group consists of destructors and copy constructors.
When you write an assignment operator, a good pattern is to first assign the right-hand operation object to a local temporary object. When the copy is completed, it is safe to destroy the existing members of the left-hand operation object. Once the resources of the left-hand operation object are destroyed, only the data will be copied from the temporary object to the members of the left-hand operation object.
To illustrate the importance of preventing self-assignment operations, consider what would happen if the assignment operator was written as follows
//It is wrong to write the assignment operator like this!HasPtr& HasPtr::operator=(const HasPtr&zhs) { delete ps;//Release the string pointed to by the object //If rhs and *this are the same object, we will copy the data from the freed memory! ps=new string(*()); i=; return*this; }
If zhs and this object are the same object, delete ps will release the string pointed to by this and rhs. Next, when we try to copy() in a new expression, we access a pointer to invalid memory, and its behavior and results are undefined.
In this way, our StrBlobPtr class can still use weak_ptr pointing to vector. Your modified class will require a copy constructor and a copy assignment operator, but no destructor is required. Explain what the copy constructs the number and copy assignment operators must do. Explain why a destructor is not needed.
Define classes that behave like pointers
For classes that behave like pointers, we need to define a copy constructor and copy assignment operator for it to copy the pointer member itself instead of the string it points to. Our class still needs its own destructor to free memory allocated by the constructor that accepts string parameters. However, in this case, the destructor cannot unilaterally release the associated string. It can release the string only when the last HasPtr pointing to the string is destroyed.
The best way to make a class show pointer-like behavior is to use shared_ptr to manage resources in the class. Copy (or assign) a shared_ptr will copy (assign) the pointer to which shared_ptr.
The shared_ptr class itself records how many users share the object it points to. When no user uses the object, the shared_ptr class is responsible for freeing resources.
However, sometimes we want to manage resources directly. In this case, using reference count is useful. To illustrate how reference counts work, we will redefine HasPtr to act like pointers, but instead of using shared_ptr, we design our own reference counts.
Quote count
Reference counting works as follows:
- In addition to initializing the object, each constructor (except for copy constructor) also creates a reference count to record how many objects share state with the object being created. When we create an object, there is only one object that shares the state, so the counter is initialized to 1.
- The copy constructor does not assign a new counter, but copies the data members of the given object, including the counter. The copy constructor increments the shared counter, indicating that the state of the given object is shared by a new user. "The destructor decrements the counter, indicating that one less user with shared state. If the counter becomes 0, the destructor releases the state.
- The copy assignment operator increments the counter of the object on the right and decrements the counter of the object on the left. If the counter of the object on the left side becomes 0, it means that its shared state has no users, and the copy assignment operator must destroy the state.
The only problem is determining where to store the reference count. The counter cannot be directly a member of the HasPtr object.
The following example illustrates the reason:
HasPtr p1("HiYa!"); HasPtr p2(p1);//p1 and p2 point to the same stringHasPtr p3(p1);//p1、p2andp3All point to the samestring
If the reference count is saved in each object, how should we update it correctly when creating p3? You can increment the counter in p1 and copy it into p3, but how do you update the counter in p2?
One way to solve this problem is to save the counter in dynamic memory. When creating an object, we also allocate a new counter. When copying or assigning an object, we copy a pointer to the counter. With this method, both the copy and the original object point to the same counter.
Define a class that uses reference counting
By using reference counting, we can write the HasPtr version of class pointer:
class HasPtr{ public: //The constructor allocates a new string and a new counter, and sets the counter to 1HasPtr(const std::string&s=std::string()): ps(newstd::string(s)),i(0),use(new std::size_t(1)){} //Refers to the Bei constructor copying all three data members and incrementing the counterHasPtr(const HasPtr &p): ps(),i(),use(){++*use;} HasPtr &operator=(const HasPtr&); ~HasPtr(); private: std::string* ps; std::size_t* use;//Use to record how many objects share *ps members};
Here we add a data member named use which records how many objects share the same string. The constructor that accepts the string parameter allocates a new counter and initializes it to 1, indicating that there is currently a user using the string member of this object.
Copy member of class pointer "tampered" reference count
When copying or assigning a HasPtr object, we want both the copy and the original object to point to the same string. That is, when copying a HasPtr, we will copy ps itself, not the string pointed to by ps. When we make a copy, the counter associated with the string is incremented.
(We define within the class) copy constructor copy all three data members of a given HasPtr. This constructor also increments the use member, indicating that ps and pointing string have a new user.
The destructor cannot unconditionally delete ps. One may have other objects pointing to this memory. The destructor should decrement the reference count, indicating that there is one less object that shares the string. If the counter becomes 0, the destructor frees the memory pointed to by ps and use:
HasPtr::~HasPtr() { if(--*use==0){//If the reference count becomes 0delete ps;//Release string memorydelete use;//Release counter memory} }
The copy assignment operator performs similar work to copy constructors and destructors as usual. That is, it must increment the reference count of the right-hand operation object (i.e., the work of copying the constructor), and decrement the reference count of the left-hand operation object, freeing the memory used when necessary (i.e., the work of the destructor).
And as usual, the assignment operator must handle self-assignment. We do this by incrementing the count in zhs first and then decrementing the count in the operation object on the left. In this way, when the two objects are the same, the counter has been incremented before we check whether ps (and use) should be released:
HasPtr& HasPtr::operator=(const HasPtr&rhs) { ++*;//Increase the reference count of the operation object on the right side if(--*use==0){//Then decrement the reference count of this object delete ps;//If there are no other users delete use;//Release the assigned member of this object } ps=;//Copy the data from rhs to this object i = ; use=; return*this;//Return to this object}
Summarize
The above is personal experience. I hope you can give you a reference and I hope you can support me more.