The Development of C++ Smart Pointer

smart pointer

GC–garbage collection garbage collection, the mechanism in Java. <memory>in the header file

memory leak

Heap memory leak (Heap leak)

Heap memory refers to a piece of memory allocated from the heap through malloc / calloc / realloc / new, etc. as needed during program execution. After use, it must be deleted by calling the corresponding free or delete. Assuming that the design error of the program causes this part of the memory to not be released, then this part of the space will no longer be used in the future, and Heap Leak will occur.

system resource leak

Refers to the resources allocated by the system used by the program, such as sockets, file descriptors, pipes, etc., which are not released using the corresponding functions, resulting in a waste of system resources, which can seriously lead to reduced system performance and unstable system execution.

Hazards of memory leaks: Memory leaks occur in long-running programs, which have a great impact, such as operating systems, background services, etc. Memory leaks will lead to slower and slower responses, and eventually freeze.

Use RAII ideas or smart pointers to manage resources.

RAII resources are initialized upon acquisition

RAII (Resource Acquisition Is Initialization) is a simple technique that uses the object life cycle to control program resources (such as memory, file handles, network connections, mutexes, etc.).
It is equivalent to binding the resource life cycle and the object life cycle together ==> get the resource when the object is constructed, then control the access to the resource to keep it valid during the object life cycle, and finally when the object is destroyed Release resources. In this way, we actually entrust the responsibility of managing a resource to an object. This approach has two major advantages: 1. There is no need to explicitly release resources; 2. In this way, the resources required by the object remain valid throughout its lifetime.

// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
public:
    //保存资源
    SmartPtr(T* ptr = nullptr)
        : _ptr(ptr)
        {}
    //释放资源
    ~SmartPtr()
    {
        if(_ptr) delete _ptr;
    }
private:
	T* _ptr;
};

The aforementioned SmartPtr cannot yet be called a smart pointer, because it does not yet have the behavior of a pointer.

C98 auto_ptr

The principle is as follows:

  1. RAII characteristics
  2. Overload operator* and operator-> with pointer-like behavior.

In the AutoPtr template class, * and -> need to be overloaded before they can be used like pointers.

template<class T>
class auto_ptr {
public:
    //保存资源
    auto_ptr(T* ptr = nullptr)
        : _ptr(ptr)
        {}
    //释放资源
    ~auto_ptr()
    {
        if(_ptr) delete _ptr;
    }
    //重载operator*
    T& operator*()
    {
        return *_ptr;
    }
    //重载operator->
    T* operator->()
    {
        return _ptr;
    }
private:
	T* _ptr;
};

Problem - object dangling

There is no copy structure in the auto_ptr class, and the constructor is a shallow copy. auto_ptr<int> auto_p1(new int); auto_ptr<int> autop2(auto_p2);Such code will cause repeated destruction of resources. The solution proposed in C98 for auto_ptrthe repeated destruction of resources is resource manager transfer, that is, only one object manages a resource, and it is auto_p1ignored. Letting auto_p2the management ==> will cause the object to be suspended , that is, auto_p1 is suspended.

auto_ptr(SmartPtr<T>& sp) :_ptr(sp.ptr) {
    // 管理权转移
    sp._ptr = nullptr;
}
auto_ptr<T>& operator=(SmartPtr<T>& sp) {
	// 检测是否为自己给自己赋值
    if (this != &sp) {
        // 释放当前对象中资源
        if (_ptr) delete _ptr;
        // 转移sp中资源到当前对象中
        _ptr = sp._ptr;
        sp._ptr = NULL;
    }
    return *this;
}

Note: The solution must not be to write a deep copy ! ! Because the original pointer is to be simulated, and this resource belongs to the user, we cannot secretly copy a copy.

C++11 unique_ptr

The scoped_ptr (copy-proof smart pointer) of boost is used for reference, but two sentences are added on the basis of auto_ptr. For detailed simulation implementation, please refer to my gitee library

unique_ptr(const unique_ptr<T>& sp) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;

C++11 shared_ptr

shared_ptr itself is thread-safe , using locks to protect the ++/– of reference counting, but the resources it manages are not thread-safe and require manual control by the user. The mutex of the shared pointer protects the thread safety of its own counter, not the thread safety of resources.

It is borrowed from boost's shared_ptr (smart pointer that can be copied). The principle is to share resources between multiple shared_ptr objects through reference counting.

  1. shared_ptr maintains a counter for each resource inside it, which is used to record that the resource is shared by several objects.
  2. When the object is destroyed (that is, the destructor is called), it means that the resource is no longer used, and the reference count of the object is reduced by one.
  3. If the reference count is 0, it means that you are the last object to use the resource, and you must release the resource; if it is not 0, it means that there are other objects using the resource besides yourself, and you cannot release the resource, otherwise other objects will be released. Into the wild pointer.

Implementation of the counter

It can be used static map<T* ptr, static int count>;, but subsequent locking is a big problem.

The simple way is to open a counter space when applying for a new resource, and let one pointer in the object point to the resource, and the other pointer point to the counter space.

The counter of the member variable should be written int* count;, which can realize the management of a single resource, so that a resource has a counter .

cannot write int count;this into a count of individual objects;

It cannot be written static int count;because static member variables belong to the entire class, and all objects can be accessed, which is equivalent to a public counter, not a counter of a single resource.

Implementation of the lock

A resource corresponds to a lock , so it is written asmutex* _mutex;

When using shared_ptr, how to ensure the thread safety of resources? **Reuse a lock to protect resources! **Note that the lock of the counter cannot be used here, because the two resources are not the same space.

The Problem - Circular References

struct ListNodeThere will be problems at the doubly linked list created by the object as shown below , because its member variables are std::shared_ptr<ListNode>of type, and both node1itself and node2._prevthe reference count that will point to node1 at the same time, and node2itself and node1._nextthe reference count that will point to node2 at the same time.

Both node1 and node2 are local objects, which will be destroyed when they go out of scope, but their _prevsum _nextcannot be used for reference counters. When are members of a class object destroyed? When the object is destroyed, its member variables will be destroyed, but node2 node1._nextwill not be destroyed until it is destroyed, and node1 will not be node2._prevdestroyed until it is destroyed, which causes an infinite loop, and no one is destroyed.

To expand, suppose there are two classes, one member of class A manages another member of class B, and one member of class B manages another member of class A, which will also cause circular references.

struct ListNode
{
    std::shared_ptr<ListNode> _prev;
    std::shared_ptr<ListNode> _next;
    ~ListNode() {std::cout << "~ListNode()" << std::endl; }//仅为观察实验现象,无其他作用
};
int main()
{
    std::shared_ptr<ListNode> node1(new ListNode);
    std::shared_ptr<ListNode> node2(new ListNode);
    node1->_next = node2;//赋值构造 会对node1的引用计数做++
    node2->_prev = node1;//赋值构造 会对node2的引用计数做++
    return 0;
}

As long as any sentence in node1->_next = node2;or is shielded , there will be no problem of circular references.node2->_prev = node1;

C++11 weak_ptr

No RAII ideas are used. Its function is to point to/access resources, but it does not participate in resource management and does not increase reference counts . Used with shared_ptr.

struct ListNode
{
    std::weak_ptr<ListNode> _prev;//使用weak_ptr来解决
    std::weak_ptr<ListNode> _next;//使用weak_ptr来解决
    ~ListNode() {std::cout << "~ListNode()" << std::endl; }//仅为观察实验现象,无其他作用
};
int main()
{
    std::shared_ptr<ListNode> node1(new ListNode);
    std::shared_ptr<ListNode> node2(new ListNode);
    node1->_next = node2;//赋值构造 会对node1的引用计数做++
    node2->_prev = node1;//赋值构造 会对node2的引用计数做++
    return 0;
}

C++11 default_delete

The above four types of pointers can only manage a single new space, because when delete releases resources, it needs to consider whether it is an array (use delete[]) or a single number (delete is enough), and must match, otherwise it may cause new[]problems delete[].

So C++11 introduces a custom deleter, and the default custom deleter uses delete.constexpr shared_ptr() noexcept;

You can see this constructor when using a custom deleter template <class U, class D> shared_ptr (U* p, D del);, where del can use function pointers, functors, and lambda expressions.

template<class T>
struct delete_array
{
	operator()(const T* ptr)
	{
		delete[] ptr;
		cout << "delete[] :" << ptr << endl;
	}
};

int main()
{
	std::shared_ptr<int> sp1(new int[10], delete_array<int>());
    //仿函数
	std::shared_ptr<string> sp2(new string[10], delete_array<string>());
    //lambda表达式
	std::shared_ptr<string> sp3(new string[10], [](string* ptr) {delete[] ptr; });
	std::shared_ptr<FILE> sp4(fopen("text.txt", "r"), [](FILE* ptr) {fclose(ptr); });
	
	return 0;
}

Transform shared_ptr with custom deleter

template<class T>
struct defaule_delete
{
    void operator()(T* ptr)
    {
        delete ptr;
    }
};

template<class T, class D = defaule_delete<T>>
class shared_ptr
{
public:
    shared_ptr(T* ptr = nullptr) :_ptr(ptr), _pRefCount(new int(1)), _mutex(new mutex)
    {}

    void Release()
    {
        int flag = 0;//标记是否需要释放锁,这是局部变量(独立栈空间)
        _mutex->lock();
        if (--(*_pRefCount) == 0)
        {
            flag = 1;
            //delete _ptr;
            _del(_ptr);
            delete _pRefCount;
        }
        _mutex->unlock();

        if (flag) delete _mutex;
    }

    ~shared_ptr()
    {
        Release();
    }
    shared_ptr(const shared_ptr<T>& sp) :_ptr(sp._ptr), _pRefCount(sp._pRefCount), _mutex(sp._mutex)
    {
        _mutex->lock();
        ++(*_pRefCount);
        _mutex->unlock();
    }
    shared_ptr<T>& operator=(const shared_ptr<T>& sp)
    {
        if (sp._ptr != _ptr)
        {
            Release();
            _ptr = sp._ptr;
            _pRefCount = sp._pRefCount;
            _mutex = sp._mutex;

            _mutex->lock();
            ++(*_pRefCount);
            _mutex->unlock();
        }

        return *this;
    }

    int use_count() { return *_pRefCount; }
    T* get()
    {
        return _ptr;
    }
    // 像指针一样使用
    T& operator*() { return *_ptr; }
    T* operator->() { return _ptr; }
    T* get() const { return _ptr; }
private:
    T* _ptr;
    int* _pRefCount;
    mutex* _mutex;
    D _del;
};

template<class T>
struct delete_array
{
    void operator()(const T* ptr)
    {
        delete[] ptr;
        //cout << "delete[]" << endl;
    }
};

struct close_file
{
    void operator()(FILE* ptr)
    {
        fclose(ptr);
    }
};
int main()
{
    shared_ptr<int, delete_array<int>> sp1(new int[5]);
    shared_ptr<string, delete_array<string>> node1(new string[5]);
    shared_ptr<FILE, close_file> file1(fopen("test.txt", "r"));
    //shared_ptr<int> sp2(new int);
    return 0;
}

Guess you like

Origin blog.csdn.net/m0_61780496/article/details/129865296
Recommended