在堆上进行内存分配时,传统的指针是 Foo *p = new Foo();,这种方式的问题在于:对象的生命周期与任何明确的所有权模型脱钩,无法从代码层面判断:

从而极易引发内存泄漏或重复释放的问题。

在栈上分配时:Foo p;Foo p(10);

所以 C++11 引入了智能指针,智能指针分为三类:

头文件

  1. 唯一所有权:unique_ptr

使用方式:

std::unique_ptr<Foo> p = std::make_unique<Foo>();

这样就用智能指针初始化好了一个类,对于 unique_str,这个资源只有一个归属,归属只能通过 std::move() 移动,不能复制。另外当智能指针析构时,所指向的对象自动析构。

  1. 共享所有权:shared_ptr

使用方式:

1:
std::shared_ptr<Foo> p1 = std::make_shared<Foo>();
auto p2 = p1;

例2:
std::shared_ptr<int> p1 = std::make_shared<int>(100);
std::shared_ptr<int> p2 = p1; // 引用计数变为 2
p1.reset(); // p1 释放,引用计数变为 1,对象还在
// p2 离开作用域,引用计数变为 0,对象销毁

这样对象就有了多个归属,只要维护的控制块的引用计数大于零,对象就还活着。当最后一个指针析构时,对象也析构。

  1. 弱引用指针:weak_ptr

weak_ptr 是为了解决 shared_ptr 循环引用的问题,它指向由 shared_ptr 指向的对象,但是不增加引用计数。

可以解决循环引用问题

在使用前需要通过.lock()观察对象是否还活着,如果活着就升级为 shared_ptr,死了则返回空。

std::shared_ptr<Foo> p1 = std::make_shared<Foo>();
std::weak_ptr<Foo> p2 = p1;

if(std::shared_ptr<Foo> temp = p2.lock()){ // 提升为 shared_ptr
     // 安全使用
}

智能指针指向 new T 分配的内存资源,删除智能指针就等于调用对象的析构函数并释放对应内存。


这里需要引入 enable_shared_from_this

对于被指向的对象自己,对象内部只有 this 裸指针,其实并不算 shared_ptr 的持有者。如果在成员函数内部,试图将“指向当前对象的 shared_ptr”存入全局容器或其他长期存活的结构中,而对象本身却无法生成一个合法的 shared_ptr,就会导致生命周期管理失效,甚至产生未定义行为。

比如:

struct Foo {
    void f() {
        // 我想“延长自己一下”
    }
};

void g() {
    auto p = std::make_shared<Foo>();
    p->f();
    // 这里 p 可能马上析构
}

在上述 f() 里边,this 指向的那个 Foo 对象(堆对象)在 f() 返回后是否还能存活,理论上是不行的:

因为对象自己是没有 shared_ptr 引用的, 它只有指向自己的 this 裸指针。所以如果想将对象自己存入全局变量,保证对象不被析构的话,需要引入 std::enable_shared_from_this<Foo>shared_from_this

std::vector<std::shared_ptr<Foo>> cache;

struct Foo : std::enable_shared_from_this<Foo> {
    void add_to_cache() {
        cache.push_back(shared_from_this());
    }
};

在上述代码中的内部逻辑:

  1. auto p = std::make_shared<Foo>(); 临时的指针 p
  2. 标准库发现 Foo 继承自 enable_shared_from_this,就会将 p 记录到 Foo 内部的 weak_ptr
  3. shared_from_thisweak_ptr 引出 shared_ptr,引用计数加一,所有权保持一致。

这里比较复杂, 详细解释一下:

template<class T>
struct enable_shared_from_this {
private:
    mutable std::weak_ptr<T> weak_this;  // 注意:是 weak_ptr,不是 shared_ptr
public:
    std::shared_ptr<T> shared_from_this() {
        return std::shared_ptr<T>(weak_this); // 把 weak_ptr “提升”为 shared_ptr
    }
};

在定义 enable_shared_from_this 时,类内会定义一个 weak_ptr,这时候的 weak_ptr 是不拥有引用计数,也没有指向对象的。
当遇到 shared_from_this 时,就会将这个 weak_ptr 升级为 shared_ptr 返回出去。(前提是:该对象此前已经被某个 shared_ptr 接管,否则 weak_ptr 为空,shared_from_this() 会抛出 std::bad_weak_ptr。)

在外部调用时,代码流程:

void g() {
    auto p = std::make_shared<Foo>();
    p->add_to_cache();
} // p 析构,但 cache 里还持有
  1. 首先创建 p 指针。
  2. p->add_to_cache(); 会将类内升级完的 shared_ptr 作为新的指针 q 指向目前 p 指向的内存,引用计数加一,cache.push_back(q) 将副本 q 加入到 cache 中。
  3. g() 结束时因为 cache 中还有引用计数为一的 shared_ptr,所以对象不会析构。

当你用 std::make_shared() 或任何 shared_ptr 来首次接管这个对象时,标准库的 shared_ptr 会做一件事(概念化):如果 Foo 继承了 enable_shared_from_this,那么把“当前控制块”写入对象内部的 weak_this。

注意:加粗字体中说明了首次接管并不等于实例化:

Foo* raw = new Foo();              // 对象已实例化
auto p  = std::shared_ptr<Foo>(raw); // 此刻才被 shared_ptr 接管