2024-05-01
编程
00
请注意,本文编写于 138 天前,最后修改于 31 天前,其中某些信息可能已经过时。

目录

1. std::unique_ptr
std::make_unique
为什么要比new更加安全?
2. std::shared_ptr
std::shared_ptr的实现原理
1. 引用计数
2. 控制块
3. std::weak_ptr

在C++中,智能指针是一类模板类,用于管理指向动态分配(堆分配)对象的指针。使用智能指针可以帮助防止内存泄漏,自动释放不再使用的对象。在本文中,我们将会对C++11标准库中所包含的三种主要的智能指针:std::unique_ptrstd::shared_ptrstd::weak_ptr展开探讨。

1. std::unique_ptr

std::unique_ptr 是一种独占所有权的智能指针,意味着任何时候只能有一个unique_ptr指向一个给定的对象。当unique_ptr被销毁时,它指向的对象也会被自动销毁。这种指针非常适合用于保证资源在作用域结束时被释放。

cpp
#include <memory> void useUniquePtr() { std::unique_ptr<int> ptr(new int(10)); // 独占指向一个int值 std::cout << *ptr << std::endl; // 输出10 // 当ptr离开作用域时,自动释放内存 }

unique_ptr 不能被复制,只能被移动,这确保了其独占所有权的语义:

cpp
std::unique_ptr<int> ptr1(new int(10)); std::unique_ptr<int> ptr2 = std::move(ptr1); // 现在ptr2拥有资源,ptr1为空

std::make_unique

std::make_unique 是 C++14 引入的一个实用函数,用于创建一个 std::unique_ptr。相比直接使用 new 来构造 std::unique_ptr要更加安全和简洁。

代码示例:

c++
#include <memory> #include <iostream> class Test { public: Test() { std::cout << "Test Created\n"; } ~Test() { std::cout << "Test Destroyed\n"; } }; int main() { std::unique_ptr<Test> ptr = std::make_unique<Test>(); return 0; }

为什么要比new更加安全?

假设你有一个函数,它需要两个 std::unique_ptr 作为参数:

cpp
void process(std::unique_ptr<Test> a, std::unique_ptr<Test> b);

如果你调用这个函数,使用 new 操作符直接创建 unique_ptr

cpp
process(std::unique_ptr<Test>(new Test()), std::unique_ptr<Test>(new Test()));

以下情况可能发生:

  1. 第一个 new Test() 调用成功,分配了内存。
  2. 第二个 new Test() 调用发生在第一个调用之后,这是编译器决定的参数评估顺序。
  3. 如果第二个 new Test() 在创建对象时抛出异常(比如因为内存不足),那么第一个 new Test() 已经分配的内存将没有机会被 std::unique_ptr 接管,因为 std::unique_ptr 的构造函数还没有被调用。

这就导致了第一个 new Test() 的内存泄漏,因为异常发生时,没有任何机制来释放已经成功分配的内存。

使用 std::make_unique

make_unique 则封装了内存分配和对象构造的过程,在内部创建对象后立即将其封装在 unique_ptr 中,使整个操作成为一个原子操作,避免了上述问题,从而提供了异常安全保障。

cpp
process(std::make_unique<Test>(), std::make_unique<Test>());

在这个例子中:

  • 每个 std::make_unique<Test>() 都是一个独立的表达式,先分配内存并构造对象,然后立即将其封装在 unique_ptr 中。
  • 如果在调用 process 之前,任何一个 make_unique 调用失败(例如,由于抛出异常),已经成功创建的 unique_ptr 将会自动释放其管理的资源。这样,即使出现异常,也不会导致内存泄漏。

这就是为什么 std::make_unique 被认为提供了更好的异常安全性:它通过确保即使在异常情况下也能正确管理资源,避免了内存泄漏的风险。

因此推荐使用 std::make_unique 来创建 unique_ptr

2. std::shared_ptr

std::shared_ptr 是一种共享所有权的智能指针。多个shared_ptr可以指向同一个对象,内部使用引用计数来跟踪有多少个指针指向同一个资源。当最后一个这样的指针被销毁时,指向的对象也会被自动销毁。

cpp
void useSharedPtr() { std::shared_ptr<int> ptr1(new int(20)); { std::shared_ptr<int> ptr2 = ptr1; // ptr1和ptr2共享所有权 std::cout << *ptr2 << std::endl; // 输出20 } // ptr2离开作用域,但对象不会被删除因为ptr1还在指向它 std::cout << *ptr1 << std::endl; // 输出20 } // 离开ptr1的作用域,对象被自动删除

也可以使用make_shared创建一个std::shared_ptr,相比new来说也会更加安全。同时可以使用use_count方法返回指向当前对象的 shared_ptr 实例的数量。这是通过引用计数机制实现的,该机制跟踪有多少个 shared_ptr实例共享同一个对象。

cpp
#include <iostream> #include <memory> class Test { public: Test() { std::cout << "Test created\n"; } ~Test() { std::cout << "Test destroyed\n"; } }; int main() { auto ptr1 = std::make_shared<Test>(); std::cout << "Use count after ptr1 creation: " << ptr1.use_count() << std::endl; { auto ptr2 = ptr1; // 复制 shared_ptr,增加引用计数 std::cout << "Use count after ptr2 creation: " << ptr1.use_count() << std::endl; } // ptr2 离开作用域,引用计数减少 std::cout << "Use count after ptr2 goes out of scope: " << ptr1.use_count() << std::endl; }

std::shared_ptr的实现原理

1. 引用计数

shared_ptr 使用一个引用计数器来跟踪有多少个 shared_ptr 实例指向同一个对象。当你创建一个 shared_ptr 时,计数器会初始化为1。当新的 shared_ptr 通过拷贝构造函数或赋值运算符复制现有的 shared_ptr 时,这个计数器会增加。当一个 shared_ptr 被销毁(例如,它离开了作用域)时,计数器会减少。当计数器回到零时,指向的对象和计数器本身会被销毁。

2. 控制块

为了管理这个计数,shared_ptr 使用一个称为“控制块”的内部结构。这个控制块通常包含:

  • 引用计数(用于管理指向对象的 shared_ptr 的数量)
  • 弱引用计数(用于管理指向对象的 weak_ptr 的数量)
  • 指向被管理对象的指针

控制块是在堆上分配的。这是因为控制块的生命周期需要独立于任何单个 shared_ptr 实例,而且必须在最后一个引用(无论是 shared_ptr 还是 weak_ptr)被销毁后,控制块也能正确地维护引用计数。

3. std::weak_ptr

std::weak_ptr 是一种不拥有资源的智能指针,主要用来解决shared_ptr相互引用导致的循环引用问题。weak_ptr 指向一个由shared_ptr管理的对象,但不会增加对象的引用计数。这意味着weak_ptr 不会阻止它指向的对象被销毁。

cpp
std::shared_ptr<int> ptr1(new int(30)); std::weak_ptr<int> weakPtr = ptr1; // 创建一个weak_ptr指向同一个int { if (std::shared_ptr<int> ptr2 = weakPtr.lock()) { // 如果对象还存在,获取一个shared_ptr std::cout << *ptr2 << std::endl; // 输出30 } } ptr1.reset(); // 销毁由ptr1管理的int对象 if (std::shared_ptr<int> ptr2 = weakPtr.lock()) { // 尝试获取shared_ptr失败 std::cout << "Object exists"; } else { std::cout << "Object has been destroyed"; // 输出这个消息 }

本文作者:Rowlet

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!