C++11 smart pointer

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

1, 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 is increased by 1, and when it expires, the reference count is decreased by 1. The smart pointer will automatically release the referenced memory resources only when the reference count is 0. 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 make_ The shared function or pass in a normal pointer through a 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 an instance object of a 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

(C++98, C++11 has been abandoned) the ownership mode is adopted.

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 its 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\u 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

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 errors in the run time through the compile time. Therefore, unique_ptr ratio auto_ptr is safer.

Another 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 source unique_ptr will exist for a period of time, and the compiler will prohibit it, such as:

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

Where \1 leaves a hanging unique_ptr(pu1), which may cause harm. And \2 will not leave a 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 that allows two assignments_ ptr .

Note: if you really want to perform an operation similar to \1, you can assign a new value to this pointer 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 possible to call the original pointer (the call will crash) after the transfer of ownership. But 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 randomly.

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

For example:

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. 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 a counting mechanism to indicate that resources are shared by several pointers. You can use the member function use_count() to view the number of resource owners. 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 will release the resource ownership, and the count will be reduced by one. When the count equals 0, the resource is released.

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

Member function:

use_count returns the number of reference counts

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

swap exchange two shared_ptr object (i.e. the object owned by the exchange)

reset relinquishes the ownership of internal objects or the change 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

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

SP is equivalent to sp.get().

share_ A simple example of PTR:

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();	//Relinquish ownership of ps1, decrease of reference count
	cout << ps1.use_count()<<endl;	//0
	cout << ps2.use_count()<<endl;	//1
}

weak_ptr

share_ptr is already very easy to use, but it has a little share_ptr smart pointers still have memory leaks. When two objects use a shared_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 an access means to management objects. weak_ptr is designed to cooperate with shared_ptr to assist shared_ptr works only from a shared_ptr or another weak_ptr object construction, its construction and destruction will not cause the reference count to increase or decrease. weak_ptr is used to solve shared_ The deadlock problem of PTR cross reference, 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 of the object, and shared_ PTRs can be converted to each other, shared_ptr can be directly assigned to it, and it can obtain shared by calling the lock function_ ptr.

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. The reference count of the two resources is 2. When the function is to be jumped out, the reference count of the two resources will be reduced by 1 when the smart pointer pa and Pb are destructed. However, the reference count of the two resources is still 1. As a result, the resources are not released when the function is jumped out (the destructors of a and B are not called). The running result does not output the contents of the destructor, resulting in memory leakage. If you change one of them to weak_ptr is OK. Let's use shared in class A_ ptr pb_, Change to weak_ptr pb_ , The operation results are as follows:

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, the count of A will be reduced by 1. When pa is destructed, the count of A will be reduced by 1, and then the count of A will be 0, and A will be released.

Note: we cannot 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:

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

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

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

lock is used to get the strong reference (shared\u PTR) of the managed object If expired is true, an empty shared is returned_ ptr; Otherwise, a shared_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_ Count of PTR internal objects

share_ptr and weak_ The core implementation of PTR
As a weak reference pointer, the implementation of weakptr depends on counter class and share_ptr assignment and construction, so first set counter and share_ Simple implementation of PTR

Simple implementation of Counter

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 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

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 provided by ptr are: construct, copy construct, assign values, dereference, and delete when the reference count is 0 through release_ ptr and cnt memory.

weak_ Simple implementation of PTR

template <class T>
class WeakPtr
{
public: //The default structure and copy structure are given. 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 convert it into share through lock_ ptr.

Tags: C++

Posted by Cynthia Blue on Thu, 02 Jun 2022 07:19:43 +0530