c + + smart pointer

Posted by pennythetuff on Sun, 19 Dec 2021 06:47:43 +0100

preface

Four smart pointers in C + +: auto_ptr, unique_ptr,shared_ptr, weak_ptr, of which the last three are supported by C++11, and the first has been abandoned by C++11.

Introduction to C++11 intelligent pointer

Smart pointer is mainly used to manage the memory allocated on the heap. It encapsulates the ordinary pointer as a stack object. When the life cycle of the stack object ends, the requested memory will be released in the destructor to prevent memory leakage. The most commonly used smart pointer type in C++ 11 is shared_ptr, which uses the reference counting method to record how many smart pointers the current memory resources are referenced by. Memory for this reference count is allocated on the heap. When a new one is added, the reference count increases by 1, and when it expires, the reference count decreases by 1. Only when the reference count is 0, the smart pointer will automatically release the referenced memory resources. Yes, shared_ When PTR is initialized, a normal pointer cannot be directly assigned to a smart pointer, because one is a pointer and the other is a class. You can use make_ The shared function or pass in a normal pointer through the constructor. And you can get ordinary pointers through the get function.

Why use smart pointers

The function of smart pointer is to manage a pointer, because there are the following situations: the requested space is forgotten to be released at the end of the function, resulting in memory leakage. Using smart pointers can largely avoid this problem, because smart pointers are a class. When they exceed the scope of the instance object of the class, the destructor of the object will be called automatically, and the destructor will automatically release resources. Therefore, the function principle of smart pointer is to automatically release the memory space at the end of the function without manually releasing the memory space.

auto_ptr

(the scheme of C++98 and C++11 have been abandoned) adopt the ownership mode.

cpp

auto_ptr<string> p1 (new string ("I reigned lonely as a cloud.")); 
auto_ptr<string> p2; 
p2 = p1; //auto_ptr will not report an error

No error will be reported at this time. p2 deprives p1 of ownership, but an error will be reported when accessing p1 when the program is running. So Auto_ The disadvantage of PTR is that there is a potential memory corruption problem!

unique_ptr

(replace auto_ptr) unique_ptr implements the concept of exclusive ownership or strict ownership, ensuring that only one smart pointer can point to the object at the same time. It is particularly useful to avoid resource leakage (for example, "after creating an object with new, you forget to call delete because of an exception").

Using the ownership model, or the above example

cpp

unique_ptr<string> p3 (new string ("auto"));   //#4
unique_ptr<string> p4;                       //#5
p4 = p3;//An error will be reported!!

The compiler considers p4=p3 illegal, which avoids the problem that p3 no longer points to valid data. A compile time error occurs when trying to copy p3, and auto_ptr can bury the hidden trouble of error in the run-time by passing the compile time. Therefore, unique_ptr ratio auto_ptr is safer.

In addition, unique_ptr is also smarter: when a program tries to put a unique_ When PTR is assigned to another, if the source is unique_ptr is a temporary right value, which is allowed by the compiler; If the source is unique_ptr will exist for some time, and the compiler will prohibit it, such as:

cpp

unique_ptr<string> pu1(new string ("hello world")); 
unique_ptr<string> pu2; 
pu2 = pu1;                                      // #1 not allowed
unique_ptr<string> pu3; 
pu3 = unique_ptr<string>(new string ("You"));   // #2 allow

Which #1 leaves the hanging unique_ptr(pu1), which may cause harm. And #2 will not leave the hanging unique_ptr because it calls unique_ptr constructor. The temporary object created by this constructor will be destroyed after its ownership is transferred to pu3. This situational behavior shows that unique_ptr is better than auto, which allows two assignments_ ptr .

Note: if you really want to perform operations similar to #1, you can assign a new value to this pointer if you want to safely reuse it. C + + has a standard library function std::move(), which allows you to put a unique_ptr is assigned to another. Although it is still possible to call the original pointer (the call will crash) after the ownership is transferred, this syntax can emphasize that you are transferring ownership, so that you can clearly know what you are doing, so as not to call the original pointer indiscriminately.

(additional: the boost::scoped_ptr of the boost library is also an exclusive smart pointer, but it does not allow the transfer of ownership. It is only responsible for one resource from beginning to end. It is more secure and cautious, but the scope of application is also narrower.)

For example:

cpp

unique_ptr<string> ps1, ps2;
ps1 = demo("hello");
ps2 = move(ps1);
ps1 = demo("alexia");
cout << *ps2 << *ps1 << endl;

shared_ptr

shared_ptr implements the concept of shared ownership. Multiple smart pointers can point to the same object, and the object and its related resources will be released when the "last reference is destroyed". From the name share, we can see that resources can be shared by multiple pointers. It uses the counting mechanism to indicate that resources are shared by several pointers. You can use through the member function_ Count() to view the number of owners of the resource. In addition to being constructed through new, you can also pass in auto_ptr, unique_ptr,weak_ptr. When we call release(), the current pointer releases the resource ownership and the count is reduced by one. When the count equals 0, the resource is released.

shared_ptr is to solve Auto_ The limitation of PTR in object ownership (auto_ptr is exclusive) provides a smart pointer that can share ownership in the mechanism of using reference counting.

Member function:

use_count returns the number of references counted

unique returns whether it is exclusive ownership (use_count is 1)

swap exchange two shared_ptr object (that is, the object owned by the exchange)

reset abandons the ownership of internal objects or changes of ownership objects, which will reduce the reference count of the original objects

get returns the internal object (pointer). Since the () method has been overloaded, it is the same as using the object directly as

cpp

shared_ptr<int> sp(new int(1)); 

SP is equivalent to sp.get().

share_ A simple example of PTR:

cpp

int main()
{
	string *s1 = new string("s1");

	shared_ptr<string> ps1(s1);
	shared_ptr<string> ps2;
	ps2 = ps1;

	cout << ps1.use_count()<<endl;	//2
	cout<<ps2.use_count()<<endl;	//2
	cout << ps1.unique()<<endl;	//0

	string *s3 = new string("s3");
	shared_ptr<string> ps3(s3);

	cout << (ps1.get()) << endl;	//033AEB48
	cout << ps3.get() << endl;	//033B2C50
	swap(ps1, ps3);	//Exchange owned objects
	cout << (ps1.get())<<endl;	//033B2C50
	cout << ps3.get() << endl;	//033AEB48

	cout << ps1.use_count()<<endl;	//1
	cout << ps2.use_count() << endl;	//2
	ps2 = ps1;
	cout << ps1.use_count()<<endl;	//2
	cout << ps2.use_count() << endl;	//2
	ps1.reset();	//Loss of ownership of ps1, decrease of reference count
	cout << ps1.use_count()<<endl;	//0
	cout << ps2.use_count()<<endl;	//1
}

weak_ptr

share_ Although PTR is already very easy to use, it has a little share_ptr smart pointer still has memory leakage. When two objects use a shared pointer to each other_ PTR member variables point to each other, which will cause circular reference and invalidate the reference count, resulting in memory leakage.

weak_ptr is a smart pointer that does not control the life cycle of an object. It points to a shared_ptr managed objects The strongly referenced shared is used for memory management of this object_ ptr, weak_ptr only provides access to management objects. weak_ The purpose of PTR design is to cooperate with shared_ptr introduces a smart pointer to assist shared_ptr works, it can only be from a shared_ptr or another weak_ptr object construction, its construction and destruction will not cause the increase or decrease of reference count. weak_ptr is used to solve shared_ The deadlock problem when PTRs refer to each other, if two shared_ptr refers to each other, then the reference count of these two pointers can never drop to 0, and the resources will never be released. It is a weak reference to an object and does not increase the reference count and shared of the object_ PTRs can be transformed into each other_ PTR can be directly assigned to it, and it can get shared by calling lock function_ ptr.

cpp

class B;	//statement
class A
{
public:
	shared_ptr<B> pb_;
	~A()
	{
		cout << "A delete\n";
	}
};

class B
{
public:
	shared_ptr<A> pa_;
	~B()
	{
		cout << "B delete\n";
	}
};

void fun()
{
	shared_ptr<B> pb(new B());
	shared_ptr<A> pa(new A());
	cout << pb.use_count() << endl;	//1
	cout << pa.use_count() << endl;	//1
	pb->pa_ = pa;
	pa->pb_ = pb;
	cout << pb.use_count() << endl;	//2
	cout << pa.use_count() << endl;	//2
}

int main()
{
	fun();
	return 0;
}

It can be seen that pa and Pb in the fun function refer to each other, and the reference count of the two resources is 2. When you want to jump out of the function, the reference count of the two resources will be reduced by 1 when the smart pointer pa and Pb are destructed, but the reference count of the two resources is still 1, As A result, the resources are not released when the function jumps out (the destructors of A and B are not called). The operation result does not output the contents of the destructor, resulting in memory leakage. If one of them is changed to weak_ptr, we change the shared_ptr pb_inclass A to weak_ptr pb_, and the operation result is as follows:

cpp

1
1
1
2
B delete
A delete

In this way, the reference of resource B is only 1 at the beginning. When pb is destructed, the count of B becomes 0 and B is released. When B is released, it will also reduce the count of A by 1. At the same time, when pa is destructed, it will reduce the count of A by 1, then the count of A is 0 and A is released.

Note: we can't pass weak_ptr directly accesses the method of the object. For example, there is a method print () in the B object. We can't access it in this way. Pa - > pb_ -> print(), because PB_ It's a weak_ptr should be converted to shared first_ PTR, such as:

cpp

shared_ptr<B> p = pa->pb_.lock();
p->print();

weak_ptr does not overload * and - > but you can use lock to get an available shared_ptr object Attention, weak_ The validity of PTR should be checked before use

expired is used to detect whether the managed object has been released. If it has been released, it returns true; Otherwise, false is returned

lock is used to obtain the shared_ptr of the managed object If expired is true, an empty shared is returned_ ptr; Otherwise, a shared is returned_ PTR, whose internal object points to weak_ptr is the same

use_count returns and shared_ Reference count of PTR shared objects

reset will break_ PTR is set to null

weak_ptr supports copying or assignment, but does not affect the corresponding shared_ptr count of internal objects

share_ptr and weak_ The core implementation of PTR

As a weak reference pointer, the implementation of weakptr depends on the counter class and share of counter_ PTR assignment and construction, so first put counter and share_ Simple implementation of PTR

Simple implementation of Counter

cpp

class Counter
{
public:
    Counter() : s(0), w(0){};
    int s;	//share_ Reference count for PTR
    int w;	//weak_ Reference count for PTR
};

The purpose of the counter object is to apply for a block of memory to store the reference cardinality. s is share_ Reference count of PTR, W is weak_ The reference count of PTR. When w is 0, the counter object is deleted.

share_ Simple implementation of PTR

cpp

template <class T>
class WeakPtr; //To use weak_ptr lock() to generate share_ For PTR, copy construction is required

template <class T>
class SharePtr
{
public:
    SharePtr(T *p = 0) : _ptr(p)
    {
        cnt = new Counter();
        if (p)
            cnt->s = 1;
        cout << "in construct " << cnt->s << endl;
    }
    ~SharePtr()
    {
        release();
    }

    SharePtr(SharePtr<T> const &s)
    {
        cout << "in copy con" << endl;
        _ptr = s._ptr;
        (s.cnt)->s++;
        cout << "copy construct" << (s.cnt)->s << endl;
        cnt = s.cnt;
    }
    SharePtr(WeakPtr<T> const &w) //To use weak_ptr lock() to generate share_ For PTR, copy construction is required
    {
        cout << "in w copy con " << endl;
        _ptr = w._ptr;
        (w.cnt)->s++;
        cout << "copy w  construct" << (w.cnt)->s << endl;
        cnt = w.cnt;
    }
    SharePtr<T> &operator=(SharePtr<T> &s)
    {
        if (this != &s)
        {
            release();
            (s.cnt)->s++;
            cout << "assign construct " << (s.cnt)->s << endl;
            cnt = s.cnt;
            _ptr = s._ptr;
        }
        return *this;
    }
    T &operator*()
    {
        return *_ptr;
    }
    T *operator->()
    {
        return _ptr;
    }
    friend class WeakPtr<T>; //Easy break_ PTR and share_ptr setting reference count and assignment

protected:
    void release()
    {
        cnt->s--;
        cout << "release " << cnt->s << endl;
        if (cnt->s < 1)
        {
            delete _ptr;
            if (cnt->w < 1)
            {
                delete cnt;
                cnt = NULL;
            }
        }
    }

private:
    T *_ptr;
    Counter *cnt;
};

share_ The function interfaces given by ptr are: construction, copy construction, assignment, dereference, and delete when the reference count is 0 through release_ ptr and cnt memory.

weak_ Simple implementation of PTR

cpp

template <class T>
class WeakPtr
{
public: //The default structure and copy structure are given, in which the copy structure cannot be constructed from the original pointer
    WeakPtr()
    {
        _ptr = 0;
        cnt = 0;
    }
    WeakPtr(SharePtr<T> &s) : _ptr(s._ptr), cnt(s.cnt)
    {
        cout << "w con s" << endl;
        cnt->w++;
    }
    WeakPtr(WeakPtr<T> &w) : _ptr(w._ptr), cnt(w.cnt)
    {
        cnt->w++;
    }
    ~WeakPtr()
    {
        release();
    }
    WeakPtr<T> &operator=(WeakPtr<T> &w)
    {
        if (this != &w)
        {
            release();
            cnt = w.cnt;
            cnt->w++;
            _ptr = w._ptr;
        }
        return *this;
    }
    WeakPtr<T> &operator=(SharePtr<T> &s)
    {
        cout << "w = s" << endl;
        release();
        cnt = s.cnt;
        cnt->w++;
        _ptr = s._ptr;
        return *this;
    }
    SharePtr<T> lock()
    {
        return SharePtr<T>(*this);
    }
    bool expired()
    {
        if (cnt)
        {
            if (cnt->s > 0)
            {
                cout << "empty" << cnt->s << endl;
                return false;
            }
        }
        return true;
    }
    friend class SharePtr<T>; //Easy break_ PTR and share_ptr setting reference count and assignment
    
protected:
    void release()
    {
        if (cnt)
        {
            cnt->w--;
            cout << "weakptr release" << cnt->w << endl;
            if (cnt->w < 1 && cnt->s < 1)
            {
                //delete cnt;
                cnt = NULL;
            }
        }
    }

private:
    T *_ptr;
    Counter *cnt;
};
  • weak_ptr is generally through share_ptr, check whether the original pointer is null through the expired function, and lock to convert it into share_ptr.

Topics: C++