在堆上进行内存分配时,传统的指针是 Foo *p = new Foo();,这种方式的问题在于:对象的生命周期与任何明确的所有权模型脱钩,无法从代码层面判断:
- 谁负责释放这块内存
- 何时释放
从而极易引发内存泄漏或重复释放的问题。
在栈上分配时:
Foo p;或Foo p(10);
所以 C++11 引入了智能指针,智能指针分为三类:
头文件
- 唯一所有权:
unique_ptr
使用方式:
std::unique_ptr<Foo> p = std::make_unique<Foo>();
这样就用智能指针初始化好了一个类,对于 unique_str,这个资源只有一个归属,归属只能通过 std::move() 移动,不能复制。另外当智能指针析构时,所指向的对象自动析构。
- 共享所有权:
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,对象销毁这样对象就有了多个归属,只要维护的控制块的引用计数大于零,对象就还活着。当最后一个指针析构时,对象也析构。
- 弱引用指针:
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());
}
};在上述代码中的内部逻辑:
auto p = std::make_shared<Foo>();临时的指针 p- 标准库发现
Foo继承自enable_shared_from_this,就会将 p 记录到Foo内部的weak_ptr - 在
shared_from_this从weak_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 里还持有- 首先创建 p 指针。
p->add_to_cache();会将类内升级完的shared_ptr作为新的指针 q 指向目前 p 指向的内存,引用计数加一,cache.push_back(q)将副本 q 加入到 cache 中。- 当
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 接管