C + + smart pointer

Posted by Nexy on Wed, 23 Feb 2022 16:30:13 +0100

Smart pointer

Why do I need smart pointers

Problems with bare pointers

Bare pointer is a common pointer defined normally. It has many problems, mainly the following:

1. It is difficult to distinguish whether it points to a single object or an array;

2. After using the pointer, it is impossible to judge whether the pointer should be destroyed, because it is impossible to judge whether the pointer "owns" the object pointed to;

3. When it is determined that the pointer needs to be destroyed, it is impossible to determine whether to delete it with the delete keyword or whether there are other special destruction mechanisms, such as destroying the pointer by passing the pointer into a specific destruction function;

4. Even if the method of destroying the pointer has been determined, it is still impossible to determine whether to use delete (destroy a single object) or
delete [] (destroy an array);

5. Assuming that the above problems are solved, it is difficult to ensure that there is and only one pointer destruction operation in all paths of the code (branch structure, jump caused by exception); Any missing path may lead to memory leakage, while multiple destruction will lead to undefined behavior;

6. In theory, there is no method to distinguish whether a pointer is suspended;

Dangling a pointer is caused by the memory pointed to by the pointer being deleted

	int *p = nullptr;
	delete p;

Working principle of intelligent pointer

A class encapsulates the pointer. When applying for a pointer, it is equivalent to applying for an object. At the end of the object's lifetime, the space pointed to by the pointer will be automatically released.

Types of smart pointers

auto_ptr(c11 abandoned, c17 abolished)

class Object
{
private:
    int value;

public:
    Object(int x)
        : value(x)
    {
        cout << "Create Object" << this << endl;
    }
    ~Object()
    {
        cout << "Destory Object" << this << endl;
    }
    int &Value()
    {
        return value;
    }
    const int &Value() const
    {
        return value;
    }
};
template <typename _Ty>
class my_auto_ptr
{
private:
    bool _Owns;
    _Ty *_Ptr;

public:
    my_auto_ptr(_Ty *p)
        : _Owns(p != nullptr), _Ptr(p)
    {
    }
    ~my_auto_ptr()
    {
        if (_Owns)
        {
            delete _Ptr;
        }
        _Owns = false;
        _Ptr = NULL;
    }
    _Ty &operator*() const
    {
        return *_Ptr;
    }

    _Ty *operator->() const
    {
        return _Ptr;
    }

    void reset(_Ty *p = NULL) 
    {
        if (_Owns)
        {
            delete _Ptr;
        }
        _Ptr = p;
    }
    _Ty *release() const //Release function to release the ownership of the pointer
    {
        if (_Owns)
        {
            _Owns = false;
            tmp = _Ptr;
            _Ptr = nullptr;
        }
        return tmp;
    }
};
int main()
{
    my_auto_ptr<Object> obj(new Object(10));
    cout << (*obj).Value() << endl;
    cout << obj->Value() << endl;
    obj.reset(new Object(20));
}

The copy constructor of autoptr and the overload of the equal sign operator are discarded because they cannot be determined.

my_auto_ptr(const my_auto_ptr &p)
        : _Owns(p._Owns)
    {
        if (_Owns)
        {
            _Ptr = p;
        }
    }

The copy constructor will cause a resource to be destructed twice when destructed, causing the program to crash

my_auto_ptr(const my_auto_ptr &p)
        : _Owns(p._Owns), _Ptr(release(p))
    {
    }

The copy constructor removes the ownership of the original smart pointer to the object, which will invalidate the original pointer.

At the same time, autoptr can't distinguish whether it is an object or a group of objects, so it can't destruct objects correctly.

unique_ptr

The only ownership smart pointer deletes the copy constructor and assignment statement in the class, and uses the mobile construction and mobile assignment in c11 standard. At the same time, the partial specialization of application class template can distinguish whether it is an object or a group of objects.

#ifndef MY_UNIQUE_PTR_H
#define MY_UNIQUE_PTR_H
#include <iostream>

using namespace std;
//Delete device
template <class _Ty>
class MyDeletor
{
public:
	// MyDeletor() = default;
	MyDeletor()
	{
	}
	void operator()(_Ty *ptr) const
	{
		if (ptr != nullptr)
		{
			delete ptr;
		}
	}
};
//Delete a group of delectors
template <class _Ty>
class MyDeletor<_Ty[]> // Partial specialization: a special case of template class. When the compiler matches, it will first judge whether it meets the specialization version
{
public:
	MyDeletor() = default;
	void operator()(_Ty *ptr) const
	{
		if (ptr != nullptr)
		{
			delete[] ptr;
		}
	}
};

// unique_ptr
template <typename _Ty, typename _Dx = MyDeletor<_Ty>>
class my_unique_ptr
{
private:
	_Ty *_Ptr;
	_Dx _Delete;

public:
	my_unique_ptr(const my_unique_ptr &) = delete;
	my_unique_ptr &operator=(const my_unique_ptr &) = delete;
	my_unique_ptr(_Ty *p = nullptr)
		: _Ptr(p)
	{
		cout << "create my_unique_ptr	" << this << endl;
	}
	my_unique_ptr(my_unique_ptr &&_Y)
	{
		_Ptr = _Y._Ptr;
		_Y._Ptr = nullptr;
		cout << " move copy my_unique_ptr : " << this << endl;
		cout << "move create my_unique_ptr" << endl;
	}
	~my_unique_ptr()
	{
		cout << "~my_unique_ptr()	" << this << endl;
		if (_Ptr != nullptr)
		{
			_Delete(_Ptr);
			_Ptr = nullptr;
		}
	}
	my_unique_ptr &operator=(my_unique_ptr &&_Y)
	{
		if (this = &_Y)
		{
			return *this;
		}
		reset(_Y.release());
		// if (_Ptr != nullptr) { _myDeletor(_Ptr); }
		//_Ptr = _Y._Ptr;
		//_Y._Ptr = nullptr;
	}
	_Ty &operator*() const
	{
		return *_Ptr;
	}
	_Ty *operator->() const
	{
		return _Ptr;
	}
	_Ty *get() const
	{
		return _Ptr;
	}
	//Overload bool type
	/*
		Application scenario:
			if(my_unique_ptr)
			{

			}
		At this point, the object acts as a bool type
	*/
	operator bool() const
	{
		return !(_Ptr == nullptr);
	}
	//Re point the unique smart pointer and release the previously pointed space.
	void reset(_Ty *p)
	{
		_Ty *old = _Ptr;
		_Ptr = p;
		if (old != nullptr)
		{
			_Delete(old);
		}
	}
	//Release ownership of the smart pointer
	_Ty *release()
	{
		_Ty *old = _Ptr;
		_Ptr = nullptr;
		return old;
	}
	//Get delegator
	_Dx &get_deleter()
	{
		return _Delete;
	}
	const _Dx &get_deleter() const
	{
		return _Delete;
	}
	void swap(my_unique_ptr _Y)
	{
		std::swap(_Ptr, _Y._Ptr);
		std::swap(_Delete, _Y._myDeletor);
	}
};

//Partial specialization of a set of objects
template <typename _Ty, typename _Dx> //_ Dx inherits the default values in the generalization
class my_unique_ptr<_Ty[], _Dx>
{
private:
	_Ty *_Ptr;
	_Dx _Delete;

public:
	my_unique_ptr(const my_unique_ptr &) = delete;
	my_unique_ptr &operator=(const my_unique_ptr &) = delete;
	my_unique_ptr(_Ty *p = nullptr)
		: _Ptr(p)
	{
		cout << "create my_unique_ptr		" << this << endl;
	}
	my_unique_ptr(my_unique_ptr &&_Y)
	{
		_Ptr = _Y._Ptr;
		_Y._Ptr = nullptr;
		cout << "move create my_unique_ptr		" << this << endl;
	}
	~my_unique_ptr()
	{
		cout << "~my_unique_ptr" << this << endl;
		if (_Ptr != nullptr)
		{
			_Delete(_Ptr);
			_Ptr = nullptr;
		}
	}
	my_unique_ptr &operator=(my_unique_ptr &&_Y)
	{
		if (this = &_Y)
		{
			return *this;
		}
		reset(_Y.release());
		// if (_Ptr != nullptr) { _myDeletor(_Ptr); }
		//_Ptr = _Y._Ptr;
		//_Y._Ptr = nullptr;
	}
	_Ty &operator*() const
	{
		return *_Ptr;
	}
	_Ty *operator->() const
	{
		return _Ptr;
	}
	_Ty *get() const
	{
		return _Ptr;
	}
	//Overload bool type
	/*
		Application scenario:
			if(my_unique_ptr)
			{

			}
		At this point, the object acts as a bool type
	*/
	operator bool() const
	{
		return !(_Ptr == nullptr);
	}
	//Re point the unique smart pointer and release the previously pointed space.
	void reset(_Ty *p)
	{
		_Ty *old = _Ptr;
		_Ptr = p;
		if (old != nullptr)
		{
			_Delete(old);
		}
	}
	//Release ownership of the smart pointer
	_Ty *release()
	{
		_Ty *old = _Ptr;
		_Ptr = nullptr;
		return old;
	}
	//Get delegator
	_Dx &get_deleter()
	{
		return _Delete;
	}
	const _Dx &get_deleter() const
	{
		return _Delete;
	}
	void swap(my_unique_ptr _Y)
	{
		std::swap(_Ptr, _Y._Ptr);
		std::swap(_Delete, _Y._myDeletor);
	}
	_Ty &operator[](size_t _Idx) const
	{
		return _Ptr[_Idx]; //
	}
};

#endif
#include "my_uniquer.h"

class Object
{
private:
    int value;

public:
    Object(int x = 0)
        : value(x)
    {
        cout << "Create Object" << this << endl;
    }
    ~Object()
    {
        cout << "Destory Object" << this << endl;
    }
};
my_unique_ptr<Object> fun()
{

    return my_unique_ptr<Object>(new Object(10));
}
int main()
{
    my_unique_ptr<Object[]> objs = new (Object[2]);
}

shared_ptr and weak_ptr

shared_ptr

A shared smart pointer allows an instance object to be shared by multiple shared objects_ PTR refers to a counter class to count.

The structure is as follows:

_ Ptr is the pointer type of the user declared object, pointing to the open space_ Rep is a counter pointer pointing to the counter structure_ A Deleter is a Deleter.

weak_ptr

Weak reference smart pointer is mainly used to solve the problem that two shared smart pointers refer to each other

The following situations:

class Child;
class Parent
{
public:
    my_shared_ptr<Child> c;

public:
    Parent()
    {
        cout << "create Parent " << endl;
    }
    ~Parent()
    {
        cout << "destory Parend " << endl;
    }
    void hi() const
    {
        cout << "hello Parent " << endl;
    }
};
class Child
{
public:
    my_shared_ptr<Parent> p;

public:
    Child()
    {
        cout << "create Child " << endl;
    }
    ~Child()
    {
        cout << "destory Child " << endl;
    }
    void hi() const
    {
        cout << "hello Child " << endl;
    }
};

The structure diagram is as follows:

Execute the following code

void fun3()
{
    my_share_ptr<Parent> parent(new Parent());
    my_share_ptr<Child> child(new Child());
    parent->c = child;
    child->p = parent;
    child->p.lock()->hi();
}

At this time, the counters are all 2. When the parent smart pointer is destructed, due to the shared_ The implementation process of PTR is –_ After the reference is decomposed into 1 - > the object cannot be counted normally.

If you want to release space normally, you need developers to release space for c and p in this class, which is contrary to the original intention of designing smart pointers. Therefore, in order to ensure the concept of generalization, weak is introduced_ PTR, use weak in this case_ PTR for cross reference, this will not happen.

Because weak_ptr just increased_ The count of Weak, then the normal destruct of the object will not be affected.

#include <iostream>
using namespace std;
#Include < atomic > / / atomic operation
template <typename _Ty>
class MyDeletor
{
public:
    MyDeletor() {}
    void operator()(_Ty *p)
    {
        if (p != nullptr)
        {
            delete p;
            p = nullptr;
        }
    }
};
template <class _Ty>
class MyDeletor<_Ty[]> // Partial specialization: a special case of template class. When the compiler matches, it will first judge whether it meets the specialization version
{
public:
    MyDeletor() = default;
    void operator()(_Ty *ptr) const
    {
        if (ptr != nullptr)
        {
            delete[] ptr;
        }
    }
};

//Shared pointer counter
template <typename _Ty>
class RefCnt
{
public:
    _Ty *mptr; //Pointer type

    atomic_int _Uses; //Shared pointer usage
    atomic_int _Weak; //Weak pointer usage

public:
    RefCnt(_Ty *p = nullptr)
        : mptr(p), _Uses(0), _Weak(0)
    {
        if (p != nullptr)
        {
            _Uses = 1;
            _Weak = 1;
        }
    }
    ~RefCnt()
    {
    }
};

template <typename _Ty, typename _Dx = MyDeletor<_Ty>>
class my_share_ptr
{

private:
    _Ty *_Ptr;
    RefCnt<_Ty> *_Rep;
    _Dx _mDeletor;

public:
    my_share_ptr(_Ty *_P = nullptr)
        : _Ptr(nullptr), _Rep(nullptr)
    {
        // cout << "my_share_ptr(_Ty *_P = nullptr)    " << this << endl;
        if (_P != nullptr)
        {
            _Ptr = _P;
            _Rep = new RefCnt<_Ty>(_P);
        }
    }
    my_share_ptr(my_share_ptr &_Y)
        : _Ptr(_Y._Ptr), _Rep(_Y._Rep)
    {
        // cout << "my_share_ptr(my_share_ptr &_Y)    " << this << endl;
        if (_Rep != nullptr)
        {
            ++_Rep->_Uses;
        }
    }
    my_share_ptr(my_share_ptr &&_Y)
        : _Ptr(nullptr), _Rep(nullptr)
    {
        // cout << "my_share_ptr(my_share_ptr &&_Y)" << endl;
        _Y.swap(*this);
    }
    ~my_share_ptr()
    {
        // cout << "~my_share_ptr()    " << this << endl;
        if (_Ptr != nullptr && --_Rep->_Uses == 0)
        {
            _mDeletor(_Ptr); //If the reference count is 0, the heap space occupied by the object is freed
            if (--_Rep->_Weak == 0)
            {
                delete _Rep; //There is no need to use the delete device. The main function of the delete device is to recycle the space pointed by the smart pointer. In the current structure, it has been recycled in the previous step.
            }
        }
    }

    my_share_ptr &operator=(const my_share_ptr &_Y)
    {
        if (this == &_Y || this->_Ptr == _Y._Ptr)
        {
            return *this;
        }
        if (_Ptr != nullptr && --_Rep->_Uses == 0)
        {
            _mDeletor(_Ptr);
            if (--_Rep->_Weak == 0)
            {
                delete _Rep; //There is no need to use the delete device. The main function of the delete device is to recycle the space pointed by the smart pointer. In the current structure, it has been recycled in the previous step.
            }
        }
        _Ptr = _Y._Ptr;
        _Rep = _Y._Rep;
        if (_Ptr != nullptr)
        {
            ++_Rep->_Uses;
        }
    }
    my_share_ptr &operator=(my_share_ptr &&_Y)
    {
        // cout << "my_share_ptr &operator=(my_share_ptr &&_Y)" << endl;
        if (this == &_Y)
        {
            return *this;
        }
        if (_Ptr != nullptr && _Y._Ptr != nullptr && _Ptr == _Y._Ptr) //When two objects are different, but the objects pointed to are the same, you need to count the objects pointed to by - 1
        {
            --_Rep->_Uses;
            //Cancel_ Ownership of Y
            _Y._Ptr = nullptr;
            _Y._Rep = nullptr;
            return *this;
        }
        if (_Ptr != nullptr && --_Rep->_Uses == 0)
        {
            _mDeletor(_Ptr);
            if (--_Rep->_Weak == 0)
            {
                delete _Rep; //There is no need to use the delete device. The main function of the delete device is to recycle the space pointed by the smart pointer. In the current structure, it has been recycled in the previous step.
            }
        }
        _Ptr = _Y._Ptr;
        _Rep = _Y._Rep;
        _Y._Ptr = nullptr;
        _Y._Rep = nullptr;
        return *this;
    }
    inline _Ty *get() const
    {
        return _Ptr;
    }
    inline _Ty &operator*() const
    {
        return *get();
    }
    inline _Ty *operator->() const
    {
        return get();
    }
    inline operator bool() const
    {
        return _Ptr != nullptr;
    }
    inline size_t use_count()
    {
        if (_Rep != nullptr)
        {
            return _Rep->_Uses;
        }
    }
    void swap(my_share_ptr &s)
    {
        std::swap(_Ptr, s._Ptr);
        std::swap(_Rep, s._Rep);
    }
    template <typename _T> //Template parameters cannot be the same
    friend class my_weak_ptr;
};

template <typename _Ty>
class my_weak_ptr
{
private:
    RefCnt<_Ty> *_Rep;

public:
    my_weak_ptr()
        : _Rep(nullptr)
    {
        // cout << "my_weak_ptr()    " << this << endl;
    }
    my_weak_ptr(const my_share_ptr<_Ty> &_Y) : _Rep(_Y._Rep)
    {
        // cout << "my_weak_ptr(const my_share_ptr<_Ty> &_Y) : _Rep(_Y._Rep)    " << this << endl;
        if (_Rep != nullptr)
        {
            ++_Rep->_Weak;
        }
    }
    my_weak_ptr(const my_weak_ptr &_Y)
        : _Rep(_Y._Rep)
    {
        // cout << "my_weak_ptr(const my_weak_ptr &_Y)    " << this << endl;

        if (_Rep != nullptr)
        {
            ++_Rep->_Weak;
        }
    }
    my_weak_ptr(my_weak_ptr &&_Y)
        : _Rep(_Y._Rep)
    {
        // cout << "my_weak_ptr(my_weak_ptr &&_Y)    " << this << endl;

        _Y._Rep = nullptr;
    }
    ~my_weak_ptr()
    {
        // cout << "~my_weak_ptr()  " << this << endl;
        if (_Rep != nullptr && --_Rep->_Weak == 0)
        {
            delete _Rep;
        }
        _Rep = nullptr;
    }

    my_weak_ptr &operator=(const my_weak_ptr &_Y)
    {
        if (this == &_Y && _Rep == _Y._Rep)
        {
            return *this;
        }
        if (_Rep != nullptr && --_Rep->_Weak == 0)
        {
            delete _Rep;
        }
        _Rep = _Y._Rep;
        if (_Rep != nullptr)
            ++_Rep->_Weak;
        return *this;
    }
    my_weak_ptr &operator=(my_weak_ptr &&_Y)
    {
        if (this == &_Y)
        {
            return *this;
        }
        if (_Rep != nullptr && _Y._Rep != nullptr && _Rep == _Y._Rep)
        {
            --_Rep->_Weak;
            _Y._Rep = nullptr;
            return *this;
        }
        if (_Rep != nullptr && --_Rep->_Weak == 0)
        {
            delete (_Rep);
        }
        _Rep = _Y._Rep;
        _Y._Rep = nullptr;
        return *this;
    }
    my_weak_ptr &operator=(my_share_ptr<_Ty> &_Y)
    {
        if (_Rep != nullptr && --_Rep->_Weak == 0)
        {
            delete _Rep;
        }
        _Rep = _Y._Rep;
        if (_Rep != nullptr)
            ++_Rep->_Weak;
        return *this;
    }
    my_weak_ptr &operator=(my_share_ptr<_Ty> &&_Y) = delete;

    bool expired() const
    {
        return _Rep->_Uses == 0;
    }
    my_share_ptr<_Ty> lock() const
    {
        my_share_ptr<_Ty> _Ret;
        _Ret._Ptr = _Rep->mptr;
        _Ret._Rep = _Rep;
        ++_Ret._Rep->_Uses; //+ + is required, otherwise
        return _Ret;
    }
};
class Object
{
private:
    int value;

public:
    Object(int x = 0) : value(x)
    {
        cerr << "Create Object      " << this << endl;
    }
    ~Object()
    {
        cerr << "Destory Object     " << this << endl;
    }
    int Value()
    {
        return value;
    }
    void Print()
    {
        cerr << value << endl;
    }
};
void fun()
{
    my_share_ptr<Object> sp1(new Object(10));
    my_weak_ptr<Object> wp1(sp1);
    my_weak_ptr<Object> wp2(sp1);
}
void fun1()
{
    my_weak_ptr<Object> wp1;

    my_share_ptr<Object> sp1(new Object(10));
    wp1 = sp1;

    if (!wp1.expired())
    {
        wp1.lock()->Print(); // wp1.lock is constructed by moving
    }
    cout << "-------" << endl;
}
class Child;
class Parent
{
public:
    my_weak_ptr<Child> c;

public:
    Parent()
    {
        cout << "create Parent " << endl;
    }
    ~Parent()
    {
        cout << "destory Parend " << endl;
    }
    void hi() const
    {
        cout << "hello Parent " << endl;
    }
};
class Child
{
public:
    my_weak_ptr<Parent> p;

public:
    Child()
    {
        cout << "create Child " << endl;
    }
    ~Child()
    {
        cout << "destory Child " << endl;
    }
    void hi() const
    {
        cout << "hello Child " << endl;
    }
};
void fun3()
{
    my_share_ptr<Parent> parent(new Parent());
    my_share_ptr<Child> child(new Child());
    parent->c = child;
    child->p = parent;
    child->p.lock()->hi();
}
int main()
{
    //The whole tectonic process
    // fun();
    // fun1();

    //Problems to be solved by weak pointer:
    //Class, if weak is not used_ PTR? The object space cannot be released normally. The destructor needs to be modified and weak is added_ The main purpose of PTR is unity
    // fun3();
}

Construct smart pointer

Generally, there are two methods to construct smart pointers. One is the above construction method, which directly assigns the object, and the other is through the make provided by the system_ Shared () or make_unique(), what is the difference between the two methods?

The difference between make construction and common method construction

It can be seen from the above implementation method that the counter in the shared pointer is an additional space. Therefore, when using the ordinary method to construct, the space is actually opened twice on the heap, as shown in the following figure:

When using the system built-in make to construct the shared pointer, only one space will be opened up on the heap, as shown in the following figure:

System accounting is good_ The total space of Rep and object size is opened up on the heap at one time.

Benefits of make constructs

1. Open up space at one time to reduce the number of memory allocation

2. Another advantage is that you can increase Cache Locality: use make_shared, the memory and native memory of the counter are arranged on the heap. In this way, all our operations to access these two memories will reduce the cache misses by half compared with the other scheme.

Supplement:

The theoretical basis of introducing Cache is the principle of program locality, including temporal locality and spatial locality. That is, the data recently accessed by the CPU will be accessed by the CPU in a short time; The data near the data accessed by the CPU will be accessed by the CPU in a short time. Therefore, if the data just accessed is cached in the Cache, it can be directly fetched from the Cache during the next access, and its speed can be improved by orders of magnitude. The data to be accessed by the CPU is cached in the Cache, which is called "hit", otherwise it is called "Miss".

3. Prevent memory leakage

For example, when the program needs to consider the execution order and exception security, the use of common method construction may lead to memory leakage, as shown in the following example

struct 0bject
{
	int i;
};
void doSomething(std::shared_ptr<Object> pt,double d);
//This function may throw an exception
double couldThrowException( );
int main()
{
	doSomething(std::shared_ptr<0bject>(new 0bject {1024}),couldThrowExcption())
	return 0;
}

Analyzing the above code, at least three things are completed before the doSomething function is called: constructing and allocating memory to the Object, constructing shared ptr and calling couldthroweexception(). C++17 introduces a more strict method to identify the construction order of function parameters, but before that, the execution order of the above three things should be as follows:

new Object();

Call couldThrowException()

Construct shared_ PTR < Object > and manage the memory developed in step 1.

Once the exception is thrown in step 2, step 3 will never happen. Therefore, there is no smart pointer to manage the memory opened in step 1, that is, the memory leak.

Disadvantages of make construction

1. May waste space

As mentioned above, due to make_shared will open up a good space on the heap at one time, which also means that it has no way to release only a part of the space. That is, when the object is weakly referenced, even if the object is destructed, it can't release the space directly - it's still waiting for the space of the counter to be released. Therefore, if the object takes up too much memory, this space will be wasted during this time.

2. Use make_shared, the first and most likely problem is make_ The shared function must be able to call the target type constructor or constructor. However, at this time, even if make_ I'm afraid it's not enough to set shared as a friend of the class, because in fact, the construction of the target type is called through an auxiliary function -- not make_shared this function.

The data to be accessed is cached in the Cache, which is called "hit", otherwise it is called "Miss".

3. Prevent memory leakage

The following situations may occur when the common methods of memory construction and execution need to be considered, such as the following

struct 0bject
{
	int i;
};
void doSomething(std::shared_ptr<Object> pt,double d);
//This function may throw an exception
double couldThrowException( );
int main()
{
	doSomething(std::shared_ptr<0bject>(new 0bject {1024}),couldThrowExcption())
	return 0;
}

Analyzing the above code, at least three things are completed before the doSomething function is called: constructing and allocating memory to the Object, constructing shared ptr and calling couldthroweexception(). C++17 introduces a more strict method to identify the construction order of function parameters, but before that, the execution order of the above three things should be as follows:

new Object();

Call couldThrowException()

Construct shared_ PTR < Object > and manage the memory developed in step 1.

Once the exception is thrown in step 2, step 3 will never happen. Therefore, there is no smart pointer to manage the memory opened in step 1, that is, the memory leak.

Disadvantages of make construction

1. May waste space

As mentioned above, due to make_shared will open up a good space on the heap at one time, which also means that it has no way to release only a part of the space. That is, when the object is weakly referenced, even if the object is destructed, it can't release the space directly - it's still waiting for the space of the counter to be released. Therefore, if the object takes up too much memory, this space will be wasted during this time.

2. Use make_shared, the first and most likely problem is make_ The shared function must be able to call the target type constructor or constructor. However, at this time, even if make_ I'm afraid it's not enough to set shared as a friend of the class, because in fact, the construction of the target type is called through an auxiliary function -- not make_shared this function.

Topics: C++ Back-end