Skip to content

shared_ptr

在标头 <memory> 定义

template< class T > class shared_ptr;

std::shared_ptr 是一种通过指针保持对象共享所有权的智能指针。多个 shared_ptr 对象可持有同一对象。

共享所有权,不是被一个shared_ptr所拥有 而是多个shared_ptr之间相互协作;shared_ptr有额外的开销,只有在确定指针会被共享的时间才采用。

工作原理:引用计数,每个shared_ptr都指向相同的内存,所以只有最后一个指向该内存的shared_ptr不再指向该内存时,这个shared_ptr才会去析构指向这块内存的对象。

语法格式std::shared_ptr<指向的类型> 智能指针名;

初始化

使用new配合来初始化

cpp
// 指向int类型的空指针p1
std::shared_ptr<int> p1;

// p2指向值为100的int型数据
std::shared_ptr<int> p2(new int(100));

// 编译失败,智能指针不允许使用隐式法进行初始化
std::shared_ptr<int> p3 = new int(200);

原始指针可以初始化shared_ptr,但是不推荐。

使用make_shared函数来初始化

make_shared函数是标准库中的函数模板,安全、高效的分配和使用shared_ptr。它能够在动态内存(堆)中创建并初始化一个对象,然后返回该对象的shared_ptr

cpp
std::shared_ptr<int> p1 = make_shared<int>(100);

std::shared_ptr<string> p2 = make_shared<string>(5, 'a');

std::shared_ptr<int> p3 = make_shared<int>();
p3 = make_shared<int>(200);	//释放p3原来所指的对象并将p3指向新对象

auto p4 = make_shared<int>(300);

使用std::make_shared初始化的好处

使用 std::make_shared<T>(args...) 相比 std::shared_ptr<T>(new T(args...)) 主要有以下几个好处:

  1. 避免额外的内存分配
    • std::make_shared 会在一次内存分配中同时分配对象本体和引用计数,而 std::shared_ptr<T>(new T(args...)) 需要两次分配(一次给 T,一次给 shared_ptr 的控制块)。
    • 这不仅减少了 malloc/free 的开销,还能提高缓存命中率。
  2. 减少异常安全问题
    • std::shared_ptr<T>(new T(args...)) 是两个独立的操作,new T(args...) 可能会抛出异常,而 shared_ptr 还未成功构造,导致内存泄漏。
    • std::make_shared 进行的是原子操作,不存在这个问题。
  3. 更高效的引用计数管理
    • 由于 std::make_shared 在一个内存块中存储对象和引用计数,指针访问时可以减少额外的缓存访问,提高运行效率。
    • std::shared_ptr<T>(new T(args...)) 由于分开分配对象和控制块,会导致额外的指针间接访问。
  4. 代码更简洁
    • auto ptr = std::make_shared<T>(args...) 比 auto ptr = std::shared_ptr<T>(new T(args...)) 更简短,可读性更好。

引用计数的增加和减少

每个shered_ptr都会记录着有多少个其他的shared_ptr指向相同的对象。

在如下几种情况下引用计数会增加1

  • 使用一个shared_ptr来初始化另一个shared_ptr的时间;
  • 作为函数实参进入函数内部的时间(需要是值传递,引用传递不会增加);
  • 作为函数的返回值时(需要有shared_ptr来接收)。

在如下几种情况下引用计数会减少1

  • shared_ptr赋予新值,使其指向其他对象;
  • 局部的shared_ptr离开其作用域;
  • 当一个shared_ptr的强引用计数减少到0,则会自动释放自己所管理(指向)的对象。

常用操作

  • use_count():返回有多少个指针指向某个对象,主要用于调试目的;

  • unique():该智能指针是否独占某个对象,也就是若只有一个智能指针指向该对象返回true,否则返回false

  • reset()

    • 不带参数时:若pi是唯一指向该对象的智能指针,那么释放pi指向的对象,并将pi置空;若不是唯一指向该对象的智能指针,那么不释放pi所指向的对象,但指向该对象的引用计数减少1,并将pi置空;
    • 带参数时(一般是一个new出来的指针):若pi是唯一指向该对象的指针,则释放pi所指向的对象,让pi指向新对象;若不是唯一指向该对象的智能指针,那么不释放pi所指向的对象,但指向该对象的引用计数减少1,让pi指向新对象。
    • 空指针也可以通过reset()来重新初始化。
  • *解引用:获得p指向的对象;

  • get():返回p中保存的指针(原始指针),小心使用 如果智能指针释放了所指向的对象,那这个原始指针也就失效了;因为考虑到有些函数的参数是原始指针,所以才引入了该函数

  • swap():交换两个智能指针所指的对象;

  • =nulltr:有两个作用。一个是将所指对象的引用计数减去1,如果引用对象变为0了,那就会释放智能指针所指的对象;二是将智能指针置空;

  • 智能指针名字作为判断条件:就是在if语句中直接使用指针的名字作为判断条件;

指定删除器

指定删除器:一定时机帮我们删除所指的对象。将delete运算符作为默认的资源析构方式;我们可以使用自己指定的删除器来取代系统默认缺省的删除器。

shared_ptr指定删除器比较简单,在参数中写上具体的删除器的函数名即可
cpp
#include <iostream>
#include <memory>
using namespace std;

void mydlt(int *p) { // 定义一个int类型的删除器
    cout << "mydlt running..." << endl;
    delete p;
}

int main() {
    std::shared_ptr<int> p1(new int(100), mydlt);
    // 下面两句任意一句都会调用mydlt
    // p1.reset();
    // p1 = nullptr;

    cout << *p1 << endl;

    cout << "---" << endl;
    return 0;
}

程序输出:

bash
100
---
mydlt running...

从程序输出可以得到结论,即使没有释放 p1 ,当离开main函数作用域时,智能指针也可以自动调用删除器。

删除器可以是lamada表达式,例如

cpp
#include <iostream>
#include <memory>
using namespace std;

int main() {
    std::shared_ptr<int> p1(new int(100), [](int *tmpp) {
        cout << "lamada dlt running..." << endl;
        delete tmpp;
    });

    return 0;
}

程序输出:

bash
lamada dlt running...

shared_ptr管理动态数组的时间,默认删除器是处理不了的,需要我们自己提供删除器

指定删除器额外说明

就算是两个不同的shared_ptr指定了不同的删除器,只要他们所指向对象的类型相同,那么这两个shared_ptr就也属于同种类型。

数组问题的解决方法

如果没有定义删除器,默认的删除器是不能处理数组的。

cpp
#include <iostream>
#include <memory>
using namespace std;

class TestClass {
public:
    TestClass() {
        cout << "TestClass is created" << endl;
    }
    ~TestClass() {
        cout << "TestClass is coming to an end" << endl;
    }
};

int main() {
    std::shared_ptr<TestClass> pt1(new TestClass[5]);

    cout << "The main function is coming to an end" << endl;
    return 0;
}

程序输出:

bash
TestClass is created
TestClass is created
TestClass is created
TestClass is created
TestClass is created
The main function is coming to an end
TestClass is coming to an end

从输出可以看到,对象创建了5个,但是只有一个被析构了。在Linux环境中通过valgrind内存泄露工具可以看到内存泄漏信息:

bash
$ valgrind --tool=memcheck --leak-check=full ./main
==843253== Memcheck, a memory error detector
==843253== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==843253== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==843253== Command: ./main
==843253==
TestClass is created
TestClass is created
TestClass is created
TestClass is created
TestClass is created
The main function is coming to an end
TestClass is coming to an end
==843253== Invalid free() / delete / delete[] / realloc()
==843253==    at 0x483D1CF: operator delete(void*, unsigned long) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==843253==    by 0x10995E: std::_Sp_counted_ptr<TestClass*, (__gnu_cxx::_Lock_policy)2>::_M_dispose() (shared_ptr_base.h:377)
==843253==    by 0x1096D1: std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() (shared_ptr_base.h:155)
==843253==    by 0x109618: std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() (shared_ptr_base.h:730)
==843253==    by 0x10959F: std::__shared_ptr<TestClass, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() (shared_ptr_base.h:1169)
==843253==    by 0x1095BF: std::shared_ptr<TestClass>::~shared_ptr() (shared_ptr.h:103)
==843253==    by 0x109371: main (shared_ptr.cc:16)
==843253==  Address 0x4da6c88 is 8 bytes inside a block of size 13 alloc'd
==843253==    at 0x483C583: operator new[](unsigned long) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==843253==    by 0x1092F4: main (shared_ptr.cc:16)
==843253==
==843253==
==843253== HEAP SUMMARY:
==843253==     in use at exit: 13 bytes in 1 blocks
==843253==   total heap usage: 4 allocs, 4 frees, 73,765 bytes allocated
==843253==
==843253== 13 bytes in 1 blocks are definitely lost in loss record 1 of 1
==843253==    at 0x483C583: operator new[](unsigned long) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==843253==    by 0x1092F4: main (shared_ptr.cc:16)
==843253==
==843253== LEAK SUMMARY:
==843253==    definitely lost: 13 bytes in 1 blocks
==843253==    indirectly lost: 0 bytes in 0 blocks
==843253==      possibly lost: 0 bytes in 0 blocks
==843253==    still reachable: 0 bytes in 0 blocks
==843253==         suppressed: 0 bytes in 0 blocks
==843253==
==843253== For lists of detected and suppressed errors, rerun with: -s
==843253== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
$
方法一:自己自定义删除器
cpp
#include <iostream>
#include <memory>
using namespace std;

class TestClass {
public:
    TestClass() {
        cout << "TestClass is created" << endl;
    }
    ~TestClass() {
        cout << "TestClass is coming to an end" << endl;
    }
};

void mydlt(TestClass* tmp) {
    delete[] tmp;
    }

int main() {
    std::shared_ptr<TestClass> pt1(new TestClass[5], mydlt);

    cout << "The main function is coming to an end" << endl;
    return 0;
}

程序输出:

bash
TestClass is created
TestClass is created
TestClass is created
TestClass is created
TestClass is created
The main function is coming to an end
TestClass is coming to an end
TestClass is coming to an end
TestClass is coming to an end
TestClass is coming to an end
TestClass is coming to an end

通过valgrind内存泄露工具可以看到内存泄漏信息:

bash
$ valgrind --tool=memcheck --leak-check=full ./main
==843262== Memcheck, a memory error detector
==843262== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==843262== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==843262== Command: ./main
==843262==
TestClass is created
TestClass is created
TestClass is created
TestClass is created
TestClass is created
The main function is coming to an end
TestClass is coming to an end
TestClass is coming to an end
TestClass is coming to an end
TestClass is coming to an end
TestClass is coming to an end
==843262==
==843262== HEAP SUMMARY:
==843262==     in use at exit: 0 bytes in 0 blocks
==843262==   total heap usage: 4 allocs, 4 frees, 73,773 bytes allocated
==843262==
==843262== All heap blocks were freed -- no leaks are possible
==843262==
==843262== For lists of detected and suppressed errors, rerun with: -s
==843262== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
$
方法二:使用default_delete()来做删除器,default_delete()是标准库里面的模板类。
cpp
#include <iostream>
using namespace std;

class TestClass {
public:
TestClass() {}
~TestClass() {}
};

int main() {
std::shared_ptr<TestClass> pt1(new TestClass[5], std::default_delete<TestClass[]>());

return 0;
}
方法三:使用C++ 17提供的新方法,在定义shared_ptr智能指针的时间在<>中添加上[],这样还可以使用[]来进行赋值。(使用14好像也可以运行)
cpp
#include <iostream>
#include <memory>
using namespace std;

class TestClass {
public:
    TestClass() {
        cout << "TestClass is created" << endl;
    }
    ~TestClass() {
        cout << "TestClass is coming to an end" << endl;
    }
};

int main() {
    std::shared_ptr<TestClass[]> pt1(new TestClass[5]);

    cout << "The main function is coming to an end" << endl;
    return 0;
}

程序输出:

bash
TestClass is created
TestClass is created
TestClass is created
TestClass is created
TestClass is created
The main function is coming to an end
TestClass is coming to an end
TestClass is coming to an end
TestClass is coming to an end
TestClass is coming to an end
TestClass is coming to an end

使用场景

shared_ptr是一种智能指针,它可以让你像操作普通指针一样操作共享指针。shared_ptr可以在不同的线程之间共享指针所指向的对象,从而避免了线程安全问题。

以下是shared_ptr的一些常见应用场景:

  1. 操作系统级别的线程安全:在操作系统中,共享指针是一个非常重要的概念,因为它们可以被多个线程同时访问。shared_ptr可以帮助你实现这种线程安全,从而避免了线程之间的竞争和冲突。
  2. 网络编程:在网络编程中,shared_ptr可以用于共享数据结构,例如套接字、文件句柄等。这样可以避免在多个线程之间复制数据的开销,提高网络编程的效率。
  3. 并发编程shared_ptr可以用于实现多线程环境下的并发编程。例如,你可以使用shared_ptr来管理共享资源,并在多个线程之间同步访问这些资源。
  4. 容器编程shared_ptr可以用于实现容器的线程安全。例如,你可以使用shared_ptr来管理共享的容器对象,并在多个线程之间同步访问这些对象。
  5. 内存管理shared_ptr可以用于实现内存的线程安全管理。例如,你可以使用shared_ptr来管理共享的内存块,并在多个线程之间同步访问这些块。

总之,shared_ptr是一种非常有用的智能指针,可以帮助你实现线程安全、网络编程、并发编程和内存管理等任务。

使用陷阱分析

虽然智能指针有其方便之处,但是也不是说使用了智能指针就可以高枕无忧了;智能指针也会有用错的情况,一旦用错也是致命的。

慎用原始指针

把一个原始指针绑定到了shared_ptr智能指针上之后就由智能指针来管理所指向对象的内存管理了,之后就不应该再使用这个原始指针来访问shared_ptr所指向的内存了,否则会发生错误。例如下面一个

cpp
#include <iostream>
#include <memory>

void func(std::shared_ptr<int> tmp) {
    return;
}

int main() {
    int *p = new int(100);
    //func(p);    //语法错误,不允许进行隐式类型转换
    func(std::shared_ptr<int>(p));//使用临时对象来作为实参,使用原始指针来初始化这个临时对象.原始指针的内存管理也就交给这个临时对象了;后面无法再使用原始指针,否则轻则引发语义错误,重则会引发程序错误
    *p = 10;	//p指向的内存已经被释放了。给一个不属于你的内存地址赋值,存在潜在风险;等于是给自己埋了个炸弹,不知道什么时间就爆了

    std::cout << *p << std::endl;

    return 0;
}

因为上面使用了临时对象来作为函数的实参,当程序运行到函数内,智能指针的引用计数是1;离开了函数的作用域,引用计数就变为0了,相应的p的内存也就被释放了,后面再使用p就会有潜在的风险。

cpp
#include <iostream>

void func(std::shared_ptr<int> tmp) {
    return;
}

int main(void) {
    int *p = new int(100);
    std::shared_ptr<int> sp(p);
    func(sp);

    *p = 10;
    std::cout << *p << std::endl;

    return 0;
}

千万记住,不要使用一个原始指针初始化多个shared_ptr

cpp
int *p = new int(100);
std::shared_ptr<int> sp1(p);
std::shared_ptr<int> sp2(p);
//错误的方式,会导致内存被释放两次而引发程序错误

std::shared_ptr<int> sp3(new int(200));
std::shared_ptr<int> sp4(sp3);
//这种初始化的方式才是正确的

慎用get()返回的指针

get()返回的原始指针不能delete,否则会异常

不能将get()返回的原始指针绑定到另一个shared_ptr

不要把this作为shared_ptr返回

要返回类对象的this指针应该使用enable_shared_from_this

从上面两张图片可以看到,无论是使用隐式初始化方法还是括号法初始化的方法都会引发程序错误;这个错误类似于使用一个原始指针初始化多个智能指针。要想解决这个问题需要使用C++标准库里面的类模板enable_shared_from_this,使类继承自enable_shared_from_this然后返回this的地方再调用shared_from_this()方法即可,具体使用范例如下

cpp
#include <iostream>

class TestClass : public std::enable_shared_from_this<TestClass> {
public:
    std::shared_ptr<TestClass> getself() {
        return shared_from_this();
    }
};

int main() {
    std::shared_ptr<TestClass> tc1(new TestClass);
    std::shared_ptr<TestClass> tc2 = tc1->getself();
    std::shared_ptr<TestClass> tc3(tc1->getself());

    return 0;
}

这个enable_shared_from_this中有一个弱指针weak_ptr,这个弱指针能够监视this,当我们调用shared_from_this()方法的时间,这个方法会调用弱指针的lock()方法返回一个强指针,并使其强引用计数加1,这就是工作原理。

避免循环引用

要解释循环引用是什么先看下面一个妖异的程序代码

cpp
#include <iostream>

class B;    //声明类B
class A {
public:
    std::shared_ptr<B> m_b;
    ~A() {
        std::cout << "类A的析构函数执行了" << std::endl;
    }
};

class B {
public:
    std::shared_ptr<A> m_a;
    ~B() {
        std::cout << "类B的析构函数执行了" << std::endl;
    }
};

int main() {
    std::shared_ptr<A> pa(new A);
    std::shared_ptr<B> pb(new B);
    pa->m_b = pb;
    pb->m_a = pa;

    return 0;
}

分析上面代码,可以发现A与B之间交叉嵌套,执行程序导致一个析构函数也没有执行;因为pa和pb的强引用计数都是2,程序运行结束最多只能变为1,不能减少到0,导致最后内存泄露32字节。

img

解决方法

在其中一个类中将强指针改为弱指针,在析构的时间会首先析构包含强指针的类。例如在类A中包含弱指针,那么就是类B首先析构

cpp
#include <iostream>

class B;
class A {
public:
    std::weak_ptr<B> m_b;
    ~A() {
        std::cout << "类A的析构函数执行了" << std::endl;
    }
};

class B {
public:
    std::shared_ptr<A> m_a;
    ~B() {
        std::cout << "类B的析构函数执行了" << std::endl;
    }
};

int main() {
    std::shared_ptr<A> pa(new A);
    std::shared_ptr<B> pb(new B);
    pa->m_b = pb;	//强引用计数只有1个,m_b是弱引用
    pb->m_a = pa;	//强引用计数有2个

    return 0;
}

其实导致此结果的原因也很简单,因为m_b是弱引用所以指向pb对象的强引用只有一个;离开作用域之后,强引用计数减为0(类B的析构函数先执行),又因为类B的析构函数执行了(其中包含类A强指针也会随之释放),导致pa的强引用计数减1;当超出pa作用域的时间强引用计数也会减1,不管哪个先减,pa的内存都能够正确释放。

性能问题

尺寸问题

shared_ptr的尺寸是原始指针的两倍,因为其内部有两个原始指针

  • 第一个原始指针指向shared_ptr所指向的对象
  • 第二个原始指针指向一个控制块,控制块内包含
    • 强引用计数
    • 弱引用计数
    • 其他数据:删除器指针、内存分配器指针

控制块是由第一个指向某对象的shared_ptr创建的,后面再有指向该对象的shared_ptrweak_ptr会共享该控制块

移动语义

cpp
std::shared_ptr<int> p1(new int(100));
std::shared_ptr<int> p2(std::move(p1));	//移动构造一个新的智能指针对象p2,p1就不再指向该对象(p1变成了空的智能指针),该对象的引用计数还是1

std::shared_ptr<int> p3;
p3 = std::move(p2);	//使用移动赋值运算符将p2指向的对象赋值给p3,p2就不再指向该对象(p2变成了空的智能指针),该对象的引用计数依旧是1

移动肯定比复制要快,复制要增加引用计数,移动不需要;移动构造函数快过拷贝构造函数;移动赋值运算符快过拷贝赋值运算符。

补充说明和使用建议

到这里我们已经掌握了shared_ptr常用的使用方法,还有一些比较晦涩的用法也可以自己慢慢探索。比如shared_ptr还可以指定分配器来解决内存分配的问题。

之前没有讲过的shared_ptr的用法除非你已经了解了,否则谨慎尝试;还有一些奇怪的写法不要轻易尝试。

优先使用make_shared()来初始化智能指针,因为有些情况下使用new来配合创建智能指针会分配两次内存,但是使用make_shared()一般只分配一次内存;但是如果要自己定义删除器只能使用new来配合创建智能指针

cpp
std::shared_ptr<string> p1(new string("Hello CPP"));          //有些资料中讲这种方式会分配两次内存,一次为字符串创建,另一次为控制块创建
std::shared_ptr<string> p2 = make_shared<string>("Hello CPP");	//只会分配一次内存

手搓shared_ptr

手搓shared_ptr(自己动手实现shared_ptr)还是比较简单的,最主要是要理解其原理。

原理分析

  • 每个 shared_ptr 实例指向同一对象的其他 shared_ptr 实例共享一个计数器。
  • 当创建新 shared_ptr 或拷贝现有 shared_ptr 时,计数器增加。
  • 当 shared_ptr 被销毁(例如通过析构函数)或重置(通过 reset 方法)时,计数器减少。
  • 当计数器为零时,对象被自动删除,通常通过调用 delete 操作符。

这种机制确保了对象在最后一个 shared_ptr 销毁时被释放,防止了内存泄漏。特别适合多线程或复杂数据结构中需要共享对象的场景。

手写shared_ptr实现

以下是手写 shared_ptr 的简化实现,适合面试场景:

cpp
namespace syss {
    // 辅助类:用于管理共享指针的引用计数
    template <class T>
    class SharedCount {
    public:
        // 构造函数,初始化资源指针并设置引用计数为1
        SharedCount(T *p) : pointer(p), count(1) {}
        // 析构函数,释放资源
        ~SharedCount() { delete pointer; }
        // 增加引用计数
        void increment() { ++count; }
        // 减少引用计数,当引用计数归零时,释放自己
        void decrement() {
            --count;
            if (count == 0) {
                delete this;
            }
        }
        // 获取指向的对象指针
        T *get() const {
            return pointer;
        }

    private:
        // 禁止拷贝构造和拷贝赋值,避免多次管理同一个资源
        SharedCount(const SharedCount &) = delete;
        SharedCount &operator=(const SharedCount &&) = delete;

    private:
        T *pointer; // 指向管理的对象
        int count;  // 共享指针的引用计数
    };

    //
    template <class T>
    class shared_ptr {
    public:
        // 默认构造函数,可以传入原始指针
        shared_ptr(T *p) : pointer(p) {
            if (p != nullptr) {
                countPtr = new SharedCount<T>(p); // 创建引用计数对象
            } else {
                countPtr = nullptr;
            }
        }
        // 拷贝构造函数,实现共享所有权
        shared_ptr(const shared_ptr &other) : pointer(other.pointer), countPtr(other.countPtr) {
            if (countPtr != nullptr) {
                countPtr->increment();
            }
        }
        // 移动构造函数,转移资源所有权,原对象归零
        shared_ptr(shared_ptr &&other) : pointer(other.pointer), countPtr(other.countPtr) {
            other.pointer = nullptr;
            other.countPtr = nullptr;
        }
        // 析构函数,减少引用计数,如果为零则释放资源
        ~shared_ptr() {
            if (countPtr != nullptr) {
                countPtr->decrement();
            }
        }

        // 接口实现
        // operator*
        T &operator*() {
            return *pointer;
        }
        // 转换为 `bool`,用于检查指针是否有效
        T *operator->() {
            if (pointer != nullptr) {
                return pointer;
            }
            // TODO: 添加错误处理
        }
        //
        operator bool() {
            return pointer != nullptr;
        }
        // reset
        void reset(T *other = nullptr) {
            if (pointer != other) { // 仅当新指针不同于当前指针时才执行
                if (countPtr != nullptr) {
                    countPtr->decrement();
                }
                if (other == nullptr) {                   // 如果参数为空
                    countPtr = new SharedCount<T>(other); // 创建新的共享计数对象
                } else {                                  // 如果参数不为空
                    countPtr = nullptr;
                }
            }
            countPtr->decrement();
        }
        // 获取原始指针
        T *get() const { return pointer; }

    private:
        T *pointer;               // 指向被管理的对象
        SharedCount<T> *countPtr; // 共享引用计数对象指针
    };
}

使用示例

cpp
#include <iostream>
#include "my-shared_ptr.cc"
using std::cout;
using std::endl;
using std::string;

class Person {
private:
    string name;
    int age;

public:
    Person(string name, int age = 18) {
        this->name = name;
        this->age = age;
        cout << "Person::Person()" << " name=" << this->name << endl;
    }
    ~Person() {
        cout << "Person::~Person()" << " name=" << this->name << endl;
    }
    void setName(string name) { this->name = name; }
    string getName() { return this->name; }
};

int main() {
    syss::shared_ptr<Person> p1(new Person("阿青"));
    syss::shared_ptr<Person> p2(p1);
    cout << "p1.name=" << p1->getName() << endl;
    p1->setName("阿晴");
    cout << "p1.name=" << p1->getName() << endl;
    cout << "p2.name=" << p2->getName() << endl;

    syss::shared_ptr<Person> p3(std::move(p1));
    cout << (p1 ? "p1可用" : "p1不可用") << endl;

    return 0;
}

实现细节

为了手写 shared_ptr,我们需要实现以下核心组件:

  1. 引用计数管理类:创建一个 SharedCount 类,负责存储对象指针和引用计数,并处理计数增减及对象销毁。
    • 构造函数初始化对象指针和计数为 1。
    • 析构函数在计数为零时删除对象。
    • increment 方法增加计数。
    • decrement 方法减少计数,并在计数为零时删除自身和对象。
  2. shared_ptr 类:实现智能指针的主要功能,包括构造函数、拷贝构造函数、移动构造函数、析构函数、赋值运算符(拷贝和移动)、以及必要操作符(如 operator->operator*)。
    • 构造函数:接受原始指针,创建 SharedCount 实例,并处理异常安全。
    • 拷贝构造函数:拷贝指针和计数器指针,增加计数。
    • 移动构造函数:转移所有权,源对象设为 null
    • 析构函数:减少计数,确保对象在计数为零时被删除。
    • 赋值运算符:处理拷贝和移动赋值,确保引用计数正确更新,并优化相同对象的情况。
    • 操作符:提供 operator-> 和 operator* 访问对象,operator bool 检查是否为 null

标准 shared_ptr 还有更多高级特性未实现,例如:

  • 线程安全:标准 shared_ptr 使用原子操作(如 std::atomic)确保多线程环境下的计数操作安全。面试版未考虑多线程,可能会导致数据竞争。
  • 数组支持:标准 shared_ptr 可通过 shared_ptr<T[]> 支持数组,但需要特殊处理。
  • constvolatile 限定:标准 shared_ptr 支持 const 指针,但简化版未实现。
  • 不完全类型:标准 shared_ptr 可用于不完全类型,但需要确保析构时类型完整。

参考资料