简 述: C++11 智能指针的深入分析,和动手实现简版的智能指针 std::shared_ptr
、std::unique_ptr
[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
背景
实现原理提前需要理解 特殊成员函数、std::exchange() C++14
、std::swap()
、 std::move()
、constexpr
、explicit
、noexcept
等,若是遗忘可参考此文
最后,Demo 实现或许不够十分完美和严谨,但对于其理解智能指针的原理和面试手写实现时候,足够。若有纰漏,请指正。
std::shared_ptr
原理
shared_ptr
的原理: 通过引用计数的方式来实现多个 shared_ptr 对象之间共享资源。通过引用计数和模板来实现 shared_ptr;构造函数定义的时候,要初始化其指针、引用计数、和 mutex
“copy assignment constructor” 除了校验是否相等、是否为空的时候、拷贝时要先释放旧资源,旧的引用计数 -1,赋值后再指向对新的资源的引用计数 +1
释放资源时,要先校验是否存在,及计数为 0 才释放;
代码
💻 win10 22H2
📎 Visual Studio 2019
📎 C++11
见 SharedPtr.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++11
见 UniquePtr.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
学习,附学习由浅入深的目录,这里你可以学到如何亲自编写这类软件的经验,这是一系列完整的教程,并且永久免费!”