3 - shared_ptr

本文将对共享型智能指针share_ptr做一些详细的介绍。

介绍

有时,多个对象或代码段需要同一指针的副本。unique_ptr不能复制,不能用于此类情形。而std::shared_ptr是一个可复制的支持共享所有权的智能指针,可以用于此类情形。

但是如果有多个shared_ptr实例引用统一资源,它们如何知道何时实际释放资源?这就要说说shared_ptr的工作原理了。

工作原理

在动态分配内存时,堆上的内存必须通过栈上的内存来寻址。也就是说栈上的指针是寻找堆内存的唯一方式。所以可以给堆内存添加一个 引用计数(Reference Counting),有几个指针指向它,它的引用计数就是几。当引用记录归零时,这块堆内存便会被释放

使用

初始化

unique_ptr必须用make_unique初始化一样,shared_ptr也必须用make_shared来初始化。

// 使用make_shared初始化
std::shared_ptr<int> shared1 = std::make_shared<int>(3);
// 构造一个副本
std::shared_ptr<int> shared2(shared1);

注意:千万不要用裸指针去初始化shared_ptr,容易出现内存泄漏的问题

int* pi = new int(10);
std::shared_ptr<int> shared1(pi);
// 不小心delete
delete pi;

这样会导致堆内存重复释放两次,发生错误。

引用计数

可以使用use_count()函数来查看shared_ptr对象的引用次数。

std::cout << shared1.use_count() << std::endl;

自定义deleter

相较于unique_ptrshared_ptr自定义deleter会更容易:

void close(FILE* filePtr)
{
    if (filePtr == nullptr)
        return ;
    fclose(filePtr);
    cout << "File Closed" << endl;
}

int main()
{
    FILE* f = fopen("data.txt", "w");
    // 将close()作为自定义的deleter
    shared_ptr<FILE> filePtr(f, close);
    // ......
}

为了演示share_ptr的自定义deleter才用了旧的C风格文件读写,这个deleter帮我们完成了文件资源的关闭操作。

创建C风格数组

如下:

std::shared_ptr<int[]> shared1 = std::make_shared<int[]>(10);

std::cout << shared1[2] << std::endl;

常用函数

unique()

该函数判断shared_ptr对象是否被独占,返回true/false

该函数已在C++17中被废弃,在C++20中被移除。原因是其实现方法并不能实现所代表的语义(多线程缺陷)

reset()

unique_ptr类似:

  • 当reset函数有参数时,改变此shared_ptr对象指向的内存
  • 当reset函数无参数时,将此shared_ptr对象置空。

如果这是最后的shared_ptr(即独占某块内存),才会释放这块内存。

get()

unique_ptr,但不推荐使用,因为操作底层指针可能会带来内存泄漏问题。

swap()

首先是shared_ptr自身提供的:

std::shared_ptr<int> shared1 = std::make_shared<int>(3);
std::shared_ptr<int> shared2 = std::make_shared<int>(5);

shared1.swap(shared2);
// 此时shared1指向5
std::cout << *shared1 << std::endl;

然后是标准库提供的:

std::swap(shared1, shared2);

推荐用自带的,因为有一些优化。

强制转换shared_ptr

正如存储某种类型的指针可以转换为存储另一种类型的指针一样,shared_ptr也能进行类似的转换:

强制转换一般指针强制转换shared_ptr
static_cast()static_pointer_cast()
dynamic_cast()dynamic_pointer_cast()
const_cast()const_pointer_cast()
reinterpret_cast()reinterpret_pointer_cast()

参考资料

  • 飘零的落花 - 现代C++详解
  • C++20高级编程
  • C++20 shared_ptr为什么移除成员函数unique() (zhihu.com)