简 述: C++11 智能指针的深入分析,和动手实现简版的智能指针 std::shared_ptrstd::unique_ptr

[TOC]


本文初发于 “偕臧的小站“,同步转载于此。


背景

实现原理提前需要理解 特殊成员函数std::exchange() C++14std::swap()std::move()constexprexplicitnoexcept 等,若是遗忘可参考此文


最后,Demo 实现或许不够十分完美和严谨,但对于其理解智能指针的原理和面试手写实现时候,足够。若有纰漏,请指正。


std::shared_ptr

原理

  • shared_ptr 的原理: 通过引用计数的方式来实现多个 shared_ptr 对象之间共享资源。

  • 通过引用计数和模板来实现 shared_ptr;构造函数定义的时候,要初始化其指针、引用计数、和 mutex

  • “copy assignment constructor” 除了校验是否相等、是否为空的时候、拷贝时要先释放旧资源,旧的引用计数 -1,赋值后再指向对新的资源的引用计数 +1

  • 释放资源时,要先校验是否存在,及计数为 0 才释放;


代码

  💻 win10 22H2 📎 Visual Studio 2019 📎 C++11SharedPtr.h

/*******************************************************************
 * Copyright (c) 2022~-023 XMuli  All rights reserved.
 * Description: C++ 实现一个核心的 shared_ptr 智能指针模板类
 ******************************************************************/
#pragma once
#include <iostream>
#include <mutex>
using namespace std;

template <typename T>
class SharedPtr
{
public:
    SharedPtr() : _ptr(nullptr), _refCount(nullptr), _pMutex(nullptr) { cout << "default constructor" << endl; };
    SharedPtr(T* obj) : _ptr(obj), _refCount(new int(1)), _pMutex(new mutex) { cout << "no default constructor" << endl; };

    SharedPtr(const SharedPtr<T>& obj)  // 其 _refCount 可以通过另外一个指针来修改,指向的是同一个地址
        : _ptr(obj._ptr)
        , _refCount(obj._refCount)
        , _pMutex(obj._pMutex)
    {
        cout << "copy constructor" << endl;
        addRefCount();
    };

    SharedPtr<T>& operator=(const SharedPtr<T>& obj)
    {
        cout << "copy assignment constructor" << endl;
        if (&obj != this) {
            if (_ptr != obj._ptr) {
                release();            // 先释放旧的资源

                _ptr = obj._ptr;
                _refCount = obj._refCount;
                _pMutex = obj._pMutex;

                addRefCount();        // 再技计数 +1
            }
        }

        return *this;
    }

    //SharedPtr(SharedPtr<T>&& obj) noexcept;
    //SharedPtr<T>& operator=(SharedPtr<T>&& obj)noexcept;

    ~SharedPtr() { cout << "destructor" << endl; release(); }
    T& operator*() { return *_ptr; }
    T* operator->() { return _ptr; }

    int useCount() { return *_refCount; }
    T* get() { return _ptr; }

private:
    void addRefCount()
    {
        cout << "addRefCount" << endl;
        _pMutex->lock();
        ++*_refCount;
        _pMutex->unlock();
    }

    void release()
    {
        cout << "release" << endl;
        bool bDelMutex = false;
        _pMutex->lock();

        if (_ptr && --*_refCount == 0) {  // 先校验是否存在,及计数为 0 才释放
            delete _ptr;
            delete _refCount;
            _ptr = nullptr;
            _refCount = nullptr;

            bDelMutex = true;
        }

        _pMutex->unlock();
        if (bDelMutex)
            delete _pMutex;
    }

private:                  // 需在构造函数中初始化
    T* _ptr;              // 指向管理资源的指针
    int* _refCount;       // 引用计数
    mutex* _pMutex;       // 计数自增非原子操作,加锁解决多线程
};

int main()
{
    SharedPtr<int> sp1(new int(10));
    SharedPtr<int> sp2(sp1);
    *sp2 = 20;
                                                                //sp1 与 sp2 在管理这部分资源,引用计数为 2
    cout << sp1.useCount() << "  *ptr:" << *sp1 << endl;		//2	 20
    cout << sp2.useCount() << "  *ptr:" << *sp2 << endl;		//2	 20
                                                                  
    SharedPtr<int> sp3(new int(30));                              
    sp2 = sp3;		                                            //sp3 赋值给它,释放管理的旧资源,引用计数-1,   
    cout << sp1.useCount() << "  *ptr:" << *sp1 << endl;        //1	 20
    cout << sp2.useCount() << "  *ptr:" << *sp2 << endl;        //2	 30
    cout << sp3.useCount() << "  *ptr:" << *sp3 << endl;        //2	 30
                                                                  
    sp1 = sp3;                                                    
    cout << sp1.useCount() << "  *ptr:" << *sp1 << endl;        //3	 30
    cout << sp2.useCount() << "  *ptr:" << *sp2 << endl;        //3	 30
    cout << sp3.useCount() << "  *ptr:" << *sp3 << endl;        //3	 30

    std::cout << "Hello World!\n";
    return 0;
}

/*****************************打印结果*******************************
no default constructor
copy constructor
addRefCount
2  *ptr:20
2  *ptr:20
no default constructor
copy assignment constructor
release
addRefCount
1  *ptr:20
2  *ptr:30
2  *ptr:30
copy assignment constructor
release
addRefCount
3  *ptr:30
3  *ptr:30
3  *ptr:30
Hello World!
destructor
release
destructor
release
destructor
release
 ******************************************************************/

Note:

  • mutex 实现了引用计数是线程安全的。但智能指针管理的对象存放在上,两个线程中同时去访问,会导致线程安全问题。

  • 书写测试时,若使用默认构造函数, 成员变量 _ptr、_refCount、_pMutex 在 release() 中容易崩溃;推荐带参的构造函数,完美运行测试


reference


std::unique_ptr

原理

  • unique_ptr的设计思路非常的粗暴:防拷贝,也就是不让拷贝和赋值
  • unique_ptr 唯一 拥有其所指对象,同一时刻只能有一个unique_ptr 指向给定对象(通过禁止拷贝语义、只有移动语义来实现

代码

  💻 win10 22H2 📎 Visual Studio 2019 📎 C++11UniquePtr.h

/*******************************************************************
 * Copyright (c) 2022~2023 XMuli  All rights reserved.
 * Description: C++ 实现一个核心的 unique_ptr 智能指针模板类;
 ******************************************************************/
#pragma once
#include <iostream>
using namespace std;

template <typename T>
class UniquePtr
{
public:
	constexpr UniquePtr() : _ptr(nullptr) { cout << "default constructor" << endl; };
	explicit  UniquePtr(T* obj) : _ptr(obj) { cout << "no default constructor" << endl; };

	UniquePtr(UniquePtr<T>&& obj) noexcept 
		: _ptr(obj._ptr) 
	{
		cout << "move constructor" << endl;
		obj._ptr = nullptr;
	}
	UniquePtr<T>& operator=(UniquePtr<T>&& obj) noexcept
	{
		cout << "move assignment constructor" << endl;
		if (&obj != this) {
			if (obj._ptr) {
                _ptr = obj._ptr;
                obj._ptr = nullptr;
			}
		}

		return *this;
	}

	UniquePtr(const UniquePtr<T>& obj) = delete;		// C++11 delete 禁止方式,C++98 用 private 来隐藏
    UniquePtr<T>& operator=(const UniquePtr<T>& obj) = delete;

	~UniquePtr()
	{
		cout << "destructor" << endl;
		if (_ptr) {
			delete _ptr;
			_ptr = nullptr;
		}
	}

	T& operator*() const { return *_ptr; }
	T* operator->() const { return _ptr; }
	T* get() const { return _ptr; }

	T* release()            // return std::exchange(_ptr, nullptr); // C++14
	{
		T* temp = _ptr;
        _ptr = nullptr;
		return temp;
	}

	void reset(T* ptr)		// std::exchange(_ptr, ptr); // C++14
	{
		_ptr = ptr;
	}

	void swap(UniquePtr<T>& obj)
	{
		std::swap(_ptr, obj._ptr);
	}

private:
	T* _ptr;
};

int main()
{
	UniquePtr<int> up1(new int(10));
	cout << "up1:" << up1.get() << "  *ptr:" << *up1 << endl;
	UniquePtr<int> up2(std::move(up1));                 // 控制权变更
	cout << "up1:" << up1.get() << endl;		        // nullptr, 此时 up1 已无控制权
	cout << "up2:" << up2.get() << "  *ptr:" << *up2 << endl;
	
	UniquePtr<int> up3(new int(30));
	UniquePtr<int> up4(new int(40));
	cout << "up3:" << up3.get() << "  *ptr:" << *up3 << endl;
	cout << "up4:" << up4.get() << "  *ptr:" << *up4 << endl;
	up3 = std::move(up2);                               // 控制权变更
	cout << "up3:" << up3.get() << "  *ptr:" << *up3 << endl;
	cout << "up4:" << up4.get() << "  *ptr:" << *up4 << endl;
	up3.swap(up4);
	cout << "up3:" << up3.get() << "  *ptr:" << *up3 << endl;
	cout << "up4:" << up4.get() << "  *ptr:" << *up4 << endl;
	
	up3.release();
	cout << "up3:" << up3.get() << endl;
	
	std::cout << "Hello World!\n";
	return 0;
}

/*****************************打印结果*******************************
no default constructor
up1:0086DEB8  *ptr:10
move constructor
up1:00000000
up2:0086DEB8  *ptr:10
no default constructor
no default constructor
up3:008656D0  *ptr:30
up4:00865700  *ptr:40
move assignment constructor
up3:0086DEB8  *ptr:10
up4:00865700  *ptr:40
up3:00865700  *ptr:40
up4:0086DEB8  *ptr:10
up3:00000000
Hello World!
destructor
destructor
destructor
destructor
 ******************************************************************/

reference


系列

QtExamples 【Studio】

欢迎 star ⭐ 和 fork 🍴 这个系列的 C++ / QT / DTK 学习,附学习由浅入深的目录,这里你可以学到如何亲自编写这类软件的经验,这是一系列完整的教程,并且永久免费!”