C++ smart pointer in depth analysis

  • 2020-04-02 01:14:55
  • OfStack

1. Why do you need smart Pointers?
Simply put, smart Pointers are designed to implement a garbage collection mechanism similar to that in Java. Java's garbage collection mechanism frees the programmer from the tedious task of memory management. After applying to use a memory area, Java will automatically help with the collection without having to worry about where and when the memory should be freed. But for efficiency and other reasons (C++ designers may be resistant to this idiotic approach to programming), C++ itself doesn't have such features, and its cumbersome and error-prone memory management has long been criticized by programmers.

Furthermore, smart Pointers exist to satisfy the need to manage pointer members in a class. Classes that contain pointer members need to pay special attention to copy control and assignment operations because when you copy a pointer, you only copy the address in the pointer, not the object that the pointer points to. Dangling Pointers can be a problem when an instance of a class is destructed.

There are generally two ways to manage pointer members in a class: One is to adopt a value semantics class, which provides value semantics to the pointer members. When the value object is copied, it will get a different new copy. A typical application of this approach is the string class. Another approach is smart Pointers, which are implemented by pointing to Shared objects.

2. Implementation overview of smart Pointers
A common implementation of smart pointer is to use reference count. The smart pointer class associates a counter with the object the class points to, and the reference count keeps track of how many objects in the class share the same pointer.
Each time a new object of the class is created, initialize the pointer and set the reference count to 1. When an object is created as a copy of another object, the copy constructor copies the pointer and increases the corresponding reference count. When assigning an object, the assignment operator reduces the reference count of the object referred to by the left operand (if the reference count is reduced to 0, the object is deleted) and increases the reference count of the object referred to by the right operand. When the destructor is called, the constructor reduces the reference count (if the reference count is reduced to 0, the underlying object is removed).
There are two classic strategies for implementing smart Pointers: introducing helper classes and using handle classes.

3. Implementation method 1: introduce auxiliary classes
In this way, a separate concrete class (RefPtr) is defined to encapsulate the pointer and the corresponding reference count.


class Point                                       //Underlying object class
{
public:
     Point(int xVal = 0, int yVal = 0):x(xVal),y(yVal) { }
     int getX() const { return x; }
     int getY() const { return y; }
     void setX(int xVal) { x = xVal; }
     void setY(int yVal) { y = yVal; }

private:
     int x,y;
};
class RefPtr                                  //Auxiliary class
{    //All members of the class have private access because you don't want users to use the class directly
     friend class SmartPtr;                                  // define Smart pointer class For friends because Smart pointer class Need direct manipulation Auxiliary class
     RefPtr(Point *ptr):p(ptr), count(1) { }
     ~RefPtr() { delete p; }
     int count;                                                     //Reference counting
     Point *p;                                                      //Underlying object pointer
};
class SmartPtr                                             //Smart pointer class
{
public:
     SmartPtr(Point *ptr):rp(new RefPtr(ptr)) { }                                 //The constructor
     SmartPtr(const SmartPtr &sp):rp(sp.rp) { ++rp->count; }            // copy The constructor
     SmartPtr& operator=(const SmartPtr& rhs) {                              //Overloads the assignment operator
     ++rhs.rp->count;                                                                        // First, the right operand Reference counting add 1 . 
     if(--rp->count == 0)                                                                     // then Reference counting Reduction of 1 , which can handle self-assignment 
        delete rp;
     rp = rhs.rp;
     return *this;
     }
    ~SmartPtr() {                                            //The destructor
    if(--rp->count == 0)                                  // when Reference counting Reduced to 0 , the delete Auxiliary class Object pointer to delete the underlying object 
         delete rp;
 }
private:
     RefPtr *rp;                                                //Auxiliary class Pointer to the object 
};
int main()
{
     Point *p1 = new Point(10, 8);
     SmartPtr sp1(p1);
     SmartPtr sp2(sp1);
     Point *p2 = new Point(5, 5);
     SmartPtr sp3(p2);
     sp3 = sp1;
     return 0;
}

The memory structure diagram using this method is as follows:
< img Alt = "" border = 0 SRC =" / / files.jb51.net/file_images/article/201307/20130713202216218.png ">
Implementation 2: use the handle class
To avoid each class that USES Pointers in the above scenario controlling the reference count itself, you can wrap the Pointers in a class. After encapsulation, the class object can appear anywhere the user class USES a pointer and behave as a pointer. We can use it like a pointer without worrying about the problems of a normal member pointer. We call such a class a handle class. When encapsulating a handle class, you need to apply for a dynamically allocated reference count space, and the pointer is stored separately from the reference count. The implementation example is as follows:

class Point                                                  //Underlying object class
{
public:
     Point(int xVal = 0, int yVal = 0):x(xVal),y(yVal) { }
     int getX() const { return x; }
     int getY() const { return y; }
     void setX(int xVal) { x = xVal; }
     void setY(int yVal) { y = yVal; }
public:
     virtual Point* clone() const {               //Virtual function that allows a handle class to assign a new copy of a known object without knowing the exact type of the object
     return new Point(*this);
 }

private:
     int x,y;
};
class D3Point : public Point                           //Derived classes
{
public:
     D3Point(int xVal, int yVal, int zVal):Point(xVal, yVal), z(zVal) { }
     int getZ() const { return z; }
     void setZ(int zVal) { z = zVal; }
public:
     D3Point* clone() const {                 //Virtual function that allows a handle class to assign a new copy of a known object without knowing the exact type of the object
  return new D3Point(*this);
 }
private:
     int z;
};
class SmartPtr
{
public:
     SmartPtr(Point *ptr = 0):p(ptr), count(new int(1)) { }                                         //The constructor
     SmartPtr(Point &point):p(point.clone()), count(new int(1)) { }                          //The constructor
     SmartPtr(const SmartPtr &sp):p(sp.p), count(sp.count) { ++*count; }             // copy The constructor
     SmartPtr& operator=(const SmartPtr &sp) {                                                   //Overloads the assignment operator
         ++*sp.count;                                           //First, increment the reference count of the right operand by 1,
         decr_use();                                             //Then subtract 1 from the reference count to handle the self-assignment
         p = sp.p;
         count = sp.count;
         return *this;
     }
    ~SmartPtr() {                                          //The destructor
          decr_use();
     }
public:                                   //These two operators are generally not implemented because we do not want the user to directly manipulate the underlying object pointer
     const Point* operator->() const {
          if(p) return p;
          else throw logic_error("Unbound Point");
     }
 const Point& operator*() const {
      if(p) return *p;
      else throw logic_error("Unbound Point");
     }
private:
    void decr_use() {
        if(--*count == 0)
        {
             delete p;
             delete count;
       }
    }
private:
     Point *p;                                      //Underlying object pointer
     int *count;                                   //Pointer to the reference count
};
int main()
{
      Point *p1 = new Point(10, 8);
      SmartPtr sp1(p1);
      SmartPtr sp2(sp1);
      D3Point *p2 = new D3Point(5, 5, 0);
      SmartPtr sp3(p2);
      return 0;
}

The memory structure diagram using this method is as follows:
< img Alt = "" border = 0 SRC =" / / files.jb51.net/file_images/article/201307/20130713202307484.png "P


Related articles: