C + + smart pointer

Smart pointer

1. Introduction and introduction of intelligent pointer

In order to facilitate programmers to solve the problem of memory leakage caused by forgetting to release a heap memory after applying for it, the concept of smart pointer is introduced. The behavior of smart pointer is similar to that of conventional pointer. The important difference is that it is responsible for automatically releasing the pointed object. The idea is RAII(Resources Acquisition Is Initialization) proposed by the father of C + +. Resource acquisition is initialization. Objects obtain resources during initialization and release resources during destruction. The smart pointer is provided after the C++11 version and is contained in the header file < memory >, shared_ptr,weak_ptr,unique_ptr,auto_ptr. Next, the four smart pointers are introduced one by one.

2. shared_ptr

​ shared_ptr shared pointers can manage object pointers together with other smart pointers by reference counting. Each time it is used, the number of references increases by 1, and each time it is destructed, the number of references decreases by 1. When the reference count returns to 0, the object is released.

Common API:

APIdescribe
get()Gets the original pointer
reset()Resetting the pointer is equivalent to destructing the smart pointer when the formal parameter is null, otherwise it points to a new heap
use_count()Reference count
unique()Unique reference pointer
swap(shared_ptr<type>& _Otherptr)The shared pointer is interchanged with the passed in shared pointer (including reference counter and original pointer)

Construction and initialization:

//Construct
shared_ptr<int>p1;

shared_ptr<int>p2(nullptr);
shared_ptr<int>p3(new int);

shared_ptr<int>p4(p3); //copy
shared_ptr<int>p5(std::move(p2)); //move

shared_ptr<int>p6(std::unique_ptr<int>(new int));	//Using unique_ptr initialization

cout << "p1.ues_count()" << p1.use_count() << endl;		//0
cout << "p2.ues_count()" << p2.use_count() << endl;		//0
cout << "p3.ues_count()" << p3.use_count() << endl;		//2
cout << "p4.ues_count()" << p4.use_count() << endl;		//2
cout << "p5.ues_count()" << p5.use_count() << endl;		//0
cout << "p6.ues_count()" << p6.use_count() << endl;		//1

//operator=
shared_ptr<int>foo;
shared_ptr<int>bar;

foo = bar; //copy

// Using make_shared for initialization
bar = std::make_shared<int>(20); 
std::unique_ptr<int>unqiue(new int(23)); 

shared_ptr<int>foo = make_shared<int>(10);
auto baz = make_shared<pair<int, int>>(20, 20);
cout << "foo:" << *foo.get() << endl;
cout << baz->first << baz->second << endl;


foo = std::move(unqiue); //move  unique_ptr

Suggestion: use make_shared for initialization. Be careful not to initialize multiple shared with one original pointer_ PTR, otherwise it will cause the problem of releasing the same memory twice. At the same time, pay attention to the problem of circular reference: that is, the memory cannot be released correctly, resulting in memory leakage. The solution to circular references will be mentioned later.

//reset
shared_ptr<int>sp; //empty
sp.reset(new int(20));
cout << "sp: " << *sp << endl;
sp.reset(new int);
*sp = 30;
cout << "sp: " << *sp << endl;

//delete
sp.reset();

// get()
int* p = new int(20);
shared_ptr<int>a(p);

Simultaneous shared_ptr overloads the *, - >, = operation symbols and endows the smart pointer with access and assignment behavior.

cout << *a.get() << endl;	//20
cout << *a << endl;			//20
cout << *p << endl;			//20

//operator ->
shared_ptr<object>op1;
shared_ptr<object>op2(new object);
op1 = op2;

cout << "op1->a:" << op1->a << endl;
// unique()
{
shared_ptr<int>p1;
cout << "p1.unique(): " << p1.unique() << endl;	//false(empty)
shared_ptr<int>p2(new int);
p1 = p2;
cout << "p1.unique(): " << p1.unique() << endl; //false(2)
cout << "p2.unique(): " << p1.unique() << endl;	//false(2)

shared_ptr<int>p3(new int);
cout << "p3.unique(): " << p3.unique() << endl; //true
}

//swap
{
shared_ptr<int>p1(new int(20));
shared_ptr<int>p2(new int(30));
shared_ptr<int>p3(p2);
p1.swap(p2);

cout << "after swap:" << endl;
cout << "*p1.get()" << *p1.get() << endl;	//30
cout << "*p2.get()" << *p2.get() << endl;	//20
cout << "p1.use_count()" << p1.use_count() << endl; //2
cout << "p2.use_count()" << p2.use_count() << endl; //1
}

3. weak_ptr

​ weak_ptr is a strong pointer weakening reference. Usually weak_ptr and shared_ptr can be used together, and shared can be used_ PTR initializes, but does not participate in shared_ptr's reference count, which has its own counter and does not participate in the object's life cycle. There are no overloads of operator * and - > whose greatest function is to assist shared_ptr works to observe the use of resources as a bystander.

Common API:

APIdescribe
lock()Gets the managed share pointer
expired()Whether the current managed pointer is invalid. If it is invalid, return True and valid false. Equivalent to use_count()==0
reset()Reset the pointer
swap(weak_ptr<type>&_weak)Swap weak_ PTR (including counter and raw pointer)

Construction:

// construct
{
    shared_ptr<int>sp(new int);
    //default
    weak_ptr<int>wp1;
    //copy

    weak_ptr<int>wp2(wp1);
    // from shared_ptr
    weak_ptr<int>wp3(sp);

    cout << "use_count:" << endl;
    cout << "wp1:" << wp1.use_count() << endl; //0
    cout << "wp1:" << wp2.use_count() << endl; //0
    cout << "wp1:" << wp3.use_count() << endl; //1
    cout << "sp:" << sp.use_count() << endl;  //1
}

lock and expired

{
    shared_ptr<int>sp1,sp2;
    weak_ptr<int>wp;

    sp1 = make_shared<int>(10);
    wp = sp1;

    sp2 = wp.lock(); //return shared_ptr
    sp1.reset();

    sp1 = wp.lock();
    //cout << *sp1 << endl;
    //cout << *sp2 << endl;
    cout << "wp_use_count:"  <<  wp.use_count() << endl; //2
    cout << "sp1_use_count:" << sp1.use_count() << endl;  //2
    cout << "sp2_use_count:" << sp2.use_count() << endl;  //2
}

// expired
{
    object obj;
    shared_ptr<object>sp = make_shared<object>();
    cout << "sp.get()->a:" << sp.get()->a << endl;

    weak_ptr<object>wp(sp);
    cout << "wp.expired():" << wp.expired() << endl; //0
    sp.reset();
    cout << "wp.expired():" << wp.expired() << endl; //1
}

swap

//swap
{
    shared_ptr<int>sp1 = make_shared<int>(20);
    shared_ptr<int>sp2 = make_shared<int>(30);
    shared_ptr<int>sp3(sp2);

    weak_ptr<int>wp1 = sp1;
    weak_ptr<int>wp2 = sp2;

    cout << "wp1 use_count:" << wp1.use_count() << endl; //1
    cout << "wp2 use_count:" << wp2.use_count() << endl; //2
    cout << "after swap:\n";
    wp1.swap(wp2);
    cout << "wp1 use_count:" << wp1.use_count() << endl; //2
    cout << "wp2 use_count:" << wp2.use_count() << endl; //1
    cout << "*wp2.lock(): " << *wp2.lock() << endl; //20
}

4. auto_ptr

​ auto_ptr is a class provided in C++98. It obtains the object management right during construction and releases it during disassembly. It is abolished in C++11 because of auto_ The insecurity of PTR. Let's look at a piece of code to see why it's not safe.

int* ptr = new int(20);
auto_ptr<int>p1(ptr);
auto_ptr<int>p2(ptr);

Running the above code, the program will crash because the memory space pointed to by the same pointer cannot be divided by two auto_ptr is managed, otherwise it will be destructed twice, which is similar to the principle of shallow copy.

Let's look at the next code:

int* ptr = new int(20);
auto_ptr<int>p1(ptr);
auto_ptr<int>p2;
p2 = p1;
cout << *p2 << endl; //20
cout << *p1 << endl; //At this time, p1 has transferred resource control, which is a wild pointer

In the above code, if auto_ptr uses the assignment operator, which actually transfers the resource control of p1 to p2 (equivalent to using std::move). After that, p1 is a wild pointer, which is very dangerous to use.

To sum up, auto_ The use of PTR is very unsafe, which extends to replace auto_ptr pointer - unique_ptr.

5. unique_ptr

​ unique_ptr is introduced in C++11 to replace unsafe auto_ptr, which holds the right to both objects (two unique_ptrs cannot point to the same object), and cannot be copied to other unique objects_ ptr. Resource management rights can only be transferred through std::move.

Common API:

APIdescribe
get()Get raw pointer
release()Release Unique_ptr, return original pointer

Construction:

// construct
{
    unique_ptr<int> up1;
    up1.reset(new int(20));
    unique_ptr<int> up2(new int(10));
    // No copy statement
	// up2 = up1; error
    
    unique_ptr<int> up3(std::move(up2));	//Transfer of control
    cout << *up3.get() << endl;				//10

    unique_ptr<int[]>up4(new int[3]{ 1,2,34 });
    cout << "up4[2]:" << up4[2] << endl;  //34
}

release

{
    unique_ptr<int> up1(new int(30));
    int* ptr = up1.release();
    cout << "* ptr:"<< * ptr << endl;
    delete ptr;
}

6. enable_shared_from_this

Understanding enable_ shared_ from_ Before this class, let's first look at a scenario, if a class object is shared_ptr management, another shared_ptr also needs the shared of this class_ PTR is initialized and written into code as follows:

class TestObejct {
public:
	shared_ptr<TestObejct> getSharedPtr() {
		shared_ptr<TestObejct> tempPtr(this);
		return tempPtr;
	}
	~TestObejct() {
		cout << "TestObejct Destruct\n";
	}
};
void enablesharedTest() {
	shared_ptr<TestObejct>p1(new TestObejct);
	shared_ptr<TestObejct>p2(p1->getSharedPtr());
	cout << p1.use_count()<< endl;	//1
	cout << p2.use_count()<< endl;	//1
}

The results are as follows:

It will be found that this class is destructed twice and shared at the same time_ The reference count for PTR is always 1. This is because both p1 and p2 think they are the only holders of the instance, so the reference count is not increased by 1. Finally, repeated release leads to program crash.

To solve this problem, we use enable_shared_from_this, change the above code to the following code:

class TestObejct:public enable_shared_from_this<TestObejct>{
public:

	shared_ptr<TestObejct> getSharedPtr() {
		return shared_from_this();
	}

	~TestObejct() {
		cout << "TestObejct Destruct\n";
	}
};

When the result is correct, we can know STD:: enable here_ shared_ from_ This enables an object (and has been managed by an std::shared_ptr object) to safely generate other additional std::shared_ptr instances share object ownership. We can see std::enable_shared_from_this source code.

template <class _Ty>
class enable_shared_from_this { // provide member functions that create shared_ptr to this
public:
    using _Esft_type = enable_shared_from_this;

    _NODISCARD shared_ptr<_Ty> shared_from_this() {
        return shared_ptr<_Ty>(_Wptr);
    }

    _NODISCARD shared_ptr<const _Ty> shared_from_this() const {
        return shared_ptr<const _Ty>(_Wptr);
    }
   ....
private:
    ...
    mutable weak_ptr<_Ty> _Wptr;  
};

It can be found that the principle of its internal implementation actually has a weak_ptr, calling shared_ from_ When this() function is used, the break is used again_ PTR initialization generates a new shared_ptr returns.

At the same time, std::enable_shared_from_this can also be shared_ After PTR is converted to raw pointer, it is converted to shared again_ ptr:

class TestObejct:public enable_shared_from_this<TestObejct>{
public:
	shared_ptr<TestObejct> getSharedPtr() {
		return shared_from_this();
	}
	~TestObejct() {
		cout << "TestObejct Destruct\n";
	}
};
void enablesharedTest() {
	shared_ptr<TestObejct>p1(new TestObejct);
	TestObejct* obj = p1.get();
	shared_ptr<TestObejct>p2;
	p2 = obj->shared_from_this();
    
	cout << p1.use_count() << endl; //2
	cout << p2.use_count() << endl; //2
}

7. Circular reference

Before understanding circular reference, let's discuss the following scenarios. There are two classes, and the member variables store shared with each other's types_ PTR, create instances of two classes at the same time, and use shared_ptr management. What are the two types of memory release at this time? Here we can write the following code:

class Child;
class Base {
public:
	Base(){}
	void SetChild(shared_ptr<Child>InChild) {
		_child = InChild;
	}
	~Base()
	{
		cout << "Base Destruct\n";
	}
private:
	shared_ptr<Child> _child;
};
class Child {
public:
	Child(){}
	void SetParent(shared_ptr<Base>InBase) {
		_Base = InBase;
	}
	~Child()
	{
		cout << "Child Destruct\n";
	}
private:
	shared_ptr<Base> _Base;
};

int main(){
    weak_ptr<Base>wp1;
	weak_ptr<Child>wp2;
	{
		shared_ptr<Base>bPtr(new Base);
		shared_ptr<Child>cPtr(new Child);
        wp1 = bPtr;
		wp2 = cPtr;
		bPtr->SetChild(cPtr);
		cPtr->SetParent(bPtr);
		cout << bPtr.use_count() << endl; //2
		cout << cPtr.use_count() << endl; //2
	}
    cout << wp1.use_count() << endl; //1
	cout << wp2.use_count() << endl; //1
	return 0;
}

result:

As you can see, the two classes are not released because the reference count of both is still 1. The reason is: at the beginning of initialization, bPtr and cPtr are copied to the shared pointers in class II private members respectively, so the reference count of the two shared pointers is increased by 1. After leaving the {} scope, the two shared pointers bPtr and cPtr begin to be destructed, and the reference number is reduced by 1. The resource control right of class II instances is only the shared pointers under class II private members (_baseand _Child), but the condition for the two pointers to be destructed is when the other class is destructed. Therefore, at this time, both sides are waiting for both sides to be destructed, resulting in infinite waiting, so neither can be destructed, resulting in memory leakage, which is circular reference.

The solution is very simple. You can specify the shared_ptr of one class as weak_ptr to break the loop.

8. Implement Shared_ptr and Weak_ptr

#pragma once
template<class ElementType>
class WeakPtr;

class RefCounter {
public:
	RefCounter() :_UseCount(1), _WeakCount(1) {};

	inline void _InCreaseUse() {
		++_UseCount;
	}


	inline unsigned int _DeCreaseUse() {
		--_UseCount;
		return _UseCount;
	}


	inline void _InCreaseWeak() {
		++_WeakCount;
	}


	inline unsigned int _DeCreaseWeak() {
		--_WeakCount;
		return _WeakCount;
	}

	inline unsigned int _GetUse()const {
		return _UseCount;
	}

	inline unsigned int _GetWeak()const {
		return _WeakCount;
	}

private:
	unsigned int _UseCount{0};  //Strong pointer references
	unsigned int _WeakCount{0}; //Weak pointer references
};

template<class ElementType>
class PtrBase {
public:
	void Destroy() {
		if (_Ptr) {
			cout << "delete " << *this->_Ptr << endl;
			delete this->_Ptr;
			this->_Ptr = nullptr;
		}
	}

	void Release() {
		if (_RefCounter) {
			delete this->_RefCounter;
			this->_RefCounter = nullptr;
		}
	}

	inline unsigned int Use_Count()const {
		return this->_RefCounter ? this->_RefCounter->_GetUse() : 0;
	}

protected:
	ElementType* _Ptr{nullptr};
	RefCounter* _RefCounter{nullptr};
};





template<class ElementType>
class SharedPtr :public PtrBase<ElementType> {
	friend class WeakPtr<ElementType>;
public:
	SharedPtr(){
		this->_Ptr = nullptr;
		this->_RefCounter = nullptr;
	}

	explicit SharedPtr(ElementType* Elem) {
		this->_Ptr = Elem;
		this->_RefCounter = new RefCounter;
	}

	SharedPtr(SharedPtr<ElementType>& Elem) {
		this->_Ptr = Elem._Ptr;
		Elem._RefCounter->_InCreaseUse();
		this->_RefCounter = Elem._RefCounter;
	}

	SharedPtr(WeakPtr<ElementType>& Elem) {
		if (Elem._Ptr) {
			this->_Ptr = Elem._Ptr;
			Elem._RefCounter->_InCreaseUse();
			this->_RefCounter = Elem._RefCounter;
		}
	}
	
	SharedPtr<ElementType>& operator=(SharedPtr<ElementType> &Elem) {
		if (this->_Ptr != Elem._Ptr) {
			this->Destroy();
			this->_Ptr = Elem._Ptr;
			this->_RefCounter->_InCreaseUse();
			this->_RefCounter = Elem._RefCounter;
		}
	}


	~SharedPtr() {
		this->reset();
	}

	inline void reset() {
		if (this->_RefCounter && this->_RefCounter->_DeCreaseUse() == 0&&this->_Ptr) {
			this->Destroy();
			if (this->_RefCounter->_GetWeak() == 0) {
				this->Release();
			}
		}
	}

	inline void reset(ElementType* Elem) {
		reset();
		this->_Ptr = Elem._Ptr;
		this->_RefCounter = Elem._RefCounter;
	}

	inline void swap(SharedPtr<ElementType>& Elem) {
		if (this->_Ptr != Elem._Ptr&&Elem._Ptr!=nullptr&&this->_Ptr!=nullptr) {
			ElementType* tempPtr = this->_Ptr;
			RefCounter* tempCounter = this->_RefCounter;
			this->_Ptr = Elem._Ptr;
			this->_RefCounter = Elem._RefCounter;
			Elem._Ptr = tempPtr;
			Elem._RefCounter = tempCounter;
		}
	}


	inline ElementType* get()const {
		return this->_Ptr;
	}

	inline bool isUnique()const {
		return this->_RefCounter ? this->_RefCounter->_GetUse() == 1 : 0;
	}

	inline ElementType*  operator->() const{
		return this->_Ptr;
	}

	inline ElementType& operator*()const{
		return *this->_Ptr;
	}
};



template<class ElementType>
class WeakPtr :public PtrBase<ElementType> {
public:
	friend class SharedPtr<ElementType>;
	WeakPtr(){
		this->_Ptr = nullptr;
		this->_RefCounter = nullptr;
	};

	explicit WeakPtr(SharedPtr<ElementType>& Elem) {
		this->_Ptr = Elem._Ptr;
		Elem._RefCounter->_InCreaseWeak();
		this->_RefCounter = Elem._RefCounter;
	}

	~WeakPtr() {
		if (this->_RefCounter&&this->_RefCounter->_DeCreaseWeak() == 0) {
			this->Destroy();
		}
	}

	WeakPtr(const WeakPtr<ElementType>& Elem) {
		if (Elem._Ptr != this->_Ptr) {
			this->Release();
		}
	}


	inline SharedPtr<ElementType>& lock() {
		SharedPtr<ElementType> temp(*this);
		return temp;
	}

	inline bool expired()const{
		return this->_Ptr ? false : true;
	}
};

Keywords: C++ Back-end

Added by chandru_cp on Mon, 08 Nov 2021 09:27:38 +0200