Principle, use and implementation of intelligent pointer in C + +

Principle, use and implementation of intelligent pointer in C + +

Transferred from: https://www.cnblogs.com/wxquare/p/4759020.html

1 function of smart pointer

Using heap memory in C + + programming is a very frequent operation. The application and release of heap memory are managed by programmers themselves. Programmers can improve the efficiency of programs by managing heap memory themselves, but on the whole, the management of heap memory is troublesome. The concept of intelligent pointer is introduced into C++11 to facilitate the management of heap memory. Using ordinary pointers is easy to cause heap memory leakage (forgetting to release), secondary release, memory leakage in case of program exceptions, etc. using intelligent pointers can better manage heap memory.

Understanding smart pointers requires the following three levels:

  1. At a shallow level, the smart pointer encapsulates the ordinary pointer by using a technology called RAII (resource acquisition, i.e. initialization), which makes the smart pointer essentially an object and behave like a pointer.

  2. The function of smart pointer is to prevent forgetting to call delete to release memory and forgetting to release memory when program exceptions enter the catch block. In addition, the release timing of the pointer is also very exquisite. Releasing the same pointer many times will cause program crash, which can be solved by smart pointer.

  3. Another function of smart pointers is to convert value semantics into reference semantics. The biggest difference between C + + and Java lies in the semantics. In Java, the following code:

    Animal a = new Animal();
    Animal b = a;
    

    Of course you know, there is only one object generated here, and a and b just control the reference of the object. But not in C + +,

    Animal a;
    Animal b = a;
    

    Here, two objects are generated. Refer to this article for value language http://www.cnblogs.com/Solstice/archive/2011/08/16/2141515.html .

2 use of smart pointer

The smart pointer is provided after the C++11 version and is contained in the header file < memory >, shared_ptr,unique_ptr,weak_ptr.

2.1 shared_ Use of PTR

shared_ptr multiple pointers point to the same object. shared_ptr uses reference count, each shared_ All copies of PTR point to the same memory. Each time it is used, the internal reference count is increased by 1. Each time it is destructed, the internal reference count is reduced by 1. When it is reduced to 0, the heap memory pointed to is automatically deleted. shared_ The reference count inside PTR is thread safe, but the reading of objects needs to be locked.

  • initialization. Smart pointer is a template class. You can specify the type. The incoming pointer is initialized by the constructor. You can also use make_shared function initialization. You cannot assign a pointer directly to a smart pointer, one is a class and the other is a pointer. For example, std::shared_ptr p4 = new int(1); The wording of is wrong
  • Copy and assign. Copy increases the reference count of the object by 1, and assignment decreases the reference count of the original object by 1. When the count is 0, the memory is automatically released. The reference count of the object pointed to later is increased by 1 to point to the later object.
  • get function gets the original pointer
  • Be careful not to initialize multiple shared with a single raw pointer_ PTR, otherwise the same memory will be released twice
  • Be careful to avoid circular references_ One of the biggest pitfalls of PTR is circular reference. Circular reference will lead to incorrect heap memory release and memory leakage. Circular reference in weak_ptr.
#include <iostream>
#include <memory>

int main() {
    {
        int a = 10;
        std::shared_ptr<int> ptra = std::make_shared<int>(a);
        std::shared_ptr<int> ptra2(ptra); //copy
        std::cout << ptra.use_count() << std::endl;

        int b = 20;
        int *pb = &a;
        //std::shared_ptr<int> ptrb = pb;  //error
        std::shared_ptr<int> ptrb = std::make_shared<int>(b);
        ptra2 = ptrb; //assign
        pb = ptrb.get(); //Get raw pointer

        std::cout << ptra.use_count() << std::endl;
        std::cout << ptrb.use_count() << std::endl;
    }
}

2.2 unique_ Use of PTR

unique_ PTR "unique" has the object it refers to, and there can only be one unique at a time_ PTR points to the given object (implemented by prohibiting copy semantics and only moving semantics). Compared with the original pointer, unique_ptr is used for its RAII, so that dynamic resources can be released in case of exceptions. The life cycle of unique_ptr pointer itself: from the creation of unique_ptr pointer to leaving the scope. When leaving the scope, if it points to an object, the object it refers to will be deleted Destroy (the delete operator is used by default, and you can specify other operations). unique_ Relationship between PTR pointer and the object it refers to: during the life cycle of smart pointer, the object it refers to can be changed. For example, when creating smart pointer, it can be specified through constructor, re specified through reset method, release ownership through release method, and transfer ownership through mobile semantics.

#include <iostream>
#include <memory>

int main() {
    {
        std::unique_ptr<int> uptr(new int(10));  //Binding dynamic objects
        //std::unique_ ptr<int> uptr2 = uptr;  // Cannot assign value
        //std::unique_ ptr<int> uptr2(uptr);  // Cannot copy
        std::unique_ptr<int> uptr2 = std::move(uptr); //Conversion ownership
        uptr2.release(); //Release ownership
    }
    //Memory freed when the scope of uptr is exceeded
}

2.3 weak_ Use of PTR

weak_ptr is to cooperate with shared_ PTR is an intelligent pointer introduced by PTR, because it does not have the behavior of ordinary pointers and does not overload operator * and - >. Its greatest function is to assist shared_ PTR works to observe the use of resources as a bystander. weak_ptr can be from a shared_ PTR or another weak_ptr object is constructed to obtain the observation right of resources. But weak_ptr has no shared resources, and its construction will not increase the pointer reference count. Use weak_ptr member function use_count() can observe the reference count of resources, and the function of another member function expired() is equivalent to use_count()==0, but faster, indicating that the observed resources (that is, the resources managed by shared_ptr) no longer exist. weak_ptr can use a very important member function, lock(), to extract from the observed shared_ PTR gets an available shared_ PTR object to manipulate resources. But when expired()==true, the lock() function will return a shared pointer that stores null pointers_ ptr.

#include <iostream>
#include <memory>

int main() {
    {
        std::shared_ptr<int> sh_ptr = std::make_shared<int>(10);
        std::cout << sh_ptr.use_count() << std::endl;

        std::weak_ptr<int> wp(sh_ptr);
        std::cout << wp.use_count() << std::endl;

        if(!wp.expired()){
            std::shared_ptr<int> sh_ptr2 = wp.lock(); //get another shared_ptr
            *sh_ptr = 100;
            std::cout << wp.use_count() << std::endl;
        }
    }
    //delete memory
}

2.4 circular reference

Consider a simple object modeling - parents and children: a parent has a child, a child knowledge / her parent. It's easy to write in Java. You don't have to worry about memory leakage or dangling pointers. As long as myChild and myParent are initialized correctly, Java programmers don't have to worry about access errors. Whether a handle is valid only needs to judge whether it is non null.

public class Parent
{
  private Child myChild;
}
public class Child
{
  private Parent myParent;
}

In C + +, you have to think about resource management. If the original pointer is used as a member, who releases Child and Parent? So how to ensure the validity of pointers? How to prevent dangling pointers? These problems are troublesome in C + + object-oriented programming. Now we can use smart pointer to transform object semantics into value semantics, and share_ptr can easily solve the problem of life cycle without worrying about dangling pointers. However, there is a problem of circular reference in this model. Note that one of the pointers should be weak_ptr.

  1. The original pointer is error prone

    #include <iostream>
    #include <memory>
    
    class Child;
    class Parent;
    
    class Parent {
    private:
        Child* myChild;
    public:
        void setChild(Child* ch) {
            this->myChild = ch;
        }
    
        void doSomething() {
            if (this->myChild) {
    
            }
        }
    
        ~Parent() {
            delete myChild;
        }
    };
    
    class Child {
    private:
        Parent* myParent;
    public:
        void setPartent(Parent* p) {
            this->myParent = p;
        }
        void doSomething() {
            if (this->myParent) {
    
            }
        }
        ~Child() {
            delete myParent;
        }
    };
    
    int main() {
        {
            Parent* p = new Parent;
            Child* c =  new Child;
            p->setChild(c);
            c->setPartent(p);
            delete c;  //only delete one
        }
        return 0;
    }
    
  2. Circular reference memory leak

    #include <iostream>
    #include <memory>
    
    class Child;
    class Parent;
    
    class Parent {
    private:
        std::shared_ptr<Child> ChildPtr;
    public:
        void setChild(std::shared_ptr<Child> child) {
            this->ChildPtr = child;
        }
    
        void doSomething() {
            if (this->ChildPtr.use_count()) {
    
            }
        }
    
        ~Parent() {
        }
    };
    
    class Child {
    private:
        std::shared_ptr<Parent> ParentPtr;
    public:
        void setPartent(std::shared_ptr<Parent> parent) {
            this->ParentPtr = parent;
        }
        void doSomething() {
            if (this->ParentPtr.use_count()) {
    
            }
        }
        ~Child() {
        }
    };
    
    int main() {
        std::weak_ptr<Parent> wpp;
        std::weak_ptr<Child> wpc;
        {
            std::shared_ptr<Parent> p(new Parent);
            std::shared_ptr<Child> c(new Child);
            p->setChild(c);
            c->setPartent(p);
            wpp = p;
            wpc = c;
            std::cout << p.use_count() << std::endl; // 2
            std::cout << c.use_count() << std::endl; // 2
        }
        std::cout << wpp.use_count() << std::endl;  // 1
        std::cout << wpc.use_count() << std::endl;  // 1
        return 0;
    }
    
  3. Right approach

    #include <iostream>
    #include <memory>
    
    class Child;
    class Parent;
    
    class Parent {
    private:
        //std::shared_ptr<Child> ChildPtr;
        std::weak_ptr<Child> ChildPtr;
    public:
        void setChild(std::shared_ptr<Child> child) {
            this->ChildPtr = child;
        }
    
        void doSomething() {
            //new shared_ptr
            if (this->ChildPtr.lock()) {
    
            }
        }
    
        ~Parent() {
        }
    };
    
    class Child {
    private:
        std::shared_ptr<Parent> ParentPtr;
    public:
        void setPartent(std::shared_ptr<Parent> parent) {
            this->ParentPtr = parent;
        }
        void doSomething() {
            if (this->ParentPtr.use_count()) {
    
            }
        }
        ~Child() {
        }
    };
    
    int main() {
        std::weak_ptr<Parent> wpp;
        std::weak_ptr<Child> wpc;
        {
            std::shared_ptr<Parent> p(new Parent);
            std::shared_ptr<Child> c(new Child);
            p->setChild(c);
            c->setPartent(p);
            wpp = p;
            wpc = c;
            std::cout << p.use_count() << std::endl; // 2
            std::cout << c.use_count() << std::endl; // 1
        }
        std::cout << wpp.use_count() << std::endl;  // 0
        std::cout << wpc.use_count() << std::endl;  // 0
        return 0;
    }
    

Design and implementation of intelligent pointer

The following is a demo of a simple smart pointer. The smart pointer class associates a counter with the object pointed to by the class, and the reference count tracks how many objects of the class share the same pointer. Each time a new object of the class is created, the pointer is initialized and the reference count is set 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 a value to an object, The assignment operator reduces the reference count of the object indicated by the left operand (if the reference count is reduced to 0, the object will be deleted) and increases the reference count of the object indicated by the right operand; when calling the destructor, the constructor reduces the reference count (if the reference count is reduced to 0, the underlying object will be deleted). Smart pointers are classes that simulate pointer actions. All smart pointers will overload the - > and * operators. Smart pointers also have many other functions. The more useful is automatic destruction. This is mainly to free memory by using the Limited scope of stack objects and the destructor of temporary objects (limited scope Implementation).

#include <iostream>
#include <memory>

template<typename T>
class SmartPointer {
private:
    T* _ptr;
    size_t* _count;
public:
    SmartPointer(T* ptr = nullptr) :
            _ptr(ptr) {
        if (_ptr) {
            _count = new size_t(1);
        } else {
            _count = new size_t(0);
        }
    }

    SmartPointer(const SmartPointer& ptr) {
        if (this != &ptr) {
            this->_ptr = ptr._ptr;
            this->_count = ptr._count;
            (*this->_count)++;
        }
    }

    SmartPointer& operator=(const SmartPointer& ptr) {
        if (this->_ptr == ptr._ptr) {
            return *this;
        }

        if (this->_ptr) {
            (*this->_count)--;
            if (this->_count == 0) {
                delete this->_ptr;
                delete this->_count;
            }
        }

        this->_ptr = ptr._ptr;
        this->_count = ptr._count;
        (*this->_count)++;
        return *this;
    }

    T& operator*() {
        assert(this->_ptr == nullptr);
        return *(this->_ptr);

    }

    T* operator->() {
        assert(this->_ptr == nullptr);
        return this->_ptr;
    }

    ~SmartPointer() {
        (*this->_count)--;
        if (*this->_count == 0) {
            delete this->_ptr;
            delete this->_count;
        }
    }

    size_t use_count(){
        return *this->_count;
    }
};

int main() {
    {
        SmartPointer<int> sp(new int(10));
        SmartPointer<int> sp2(sp);
        SmartPointer<int> sp3(new int(20));
        sp2 = sp3;
        std::cout << sp.use_count() << std::endl;
        std::cout << sp3.use_count() << std::endl;
    }
    //delete operator
}

reference resources:

  1. Value semantics: http://www.cnblogs.com/Solstice/archive/2011/08/16/2141515.html
  2. shared_ptr usage: http://www.cnblogs.com/jiayayao/archive/2016/12/03/6128877.html
  3. unique_ptr usage: http://blog.csdn.net/pi9nc/article/details/12227887
  4. weak_ Use of PTR: http://blog.csdn.net/mmzsyx/article/details/8090849
  5. weak_ptr solves the problem of circular reference: http://blog.csdn.net/shanno/article/details/7363480
  6. C + + interview question (IV) -- principle and implementation of intelligent pointer

Keywords: C++ Back-end

Added by henryhund on Tue, 04 Jan 2022 10:31:20 +0200