智能指针
智能指针(Smart Pointer)是一种管理堆内存的智能方式,它通过重载运算符模拟普通指针的行为,但可以自动管理资源的释放。智能指针主要解决原始指针易导致的内存泄漏问题。
C++11中引入了三种智能指针:
- unique_ptr: 独占式拥有权的智能指针,采用独占/拷贝语义。
- shared_ptr: 共享式拥有权的智能指针,引用计数机制。
- weak_ptr: 弱引用指针,需要配合shared_ptr使用。
智能指针声明时需要包含头文件memory,并指定指针类型: 智能指针支持解引用操作符*和->来访问资源:
#include <memory>
std::unique_ptr<int> p1(new int(1));
当智能指针离开作用域时,会自动释放占有的堆内存。
独占式智能指针unique_ptr
- 它不能拷贝,只能移动语义转移所有权
- 通过release()可以释放控制权
- 支持数组形式但有局限
auto p1 = std::make_unique<int>(1);
auto p2 = std::move(p1); // p1无效
auto p = p1.release(); //释放控制权
delete p;
std::unique_ptr<int[]> arr(new int[10]);
shared_ptr实现共享式拥有权
- 内部使用引用计数追踪共享情况
- 支持拷贝语义,所有权共享使用
- circurlar dependency问题
auto p1 = std::make_shared<int>(10);
auto p2 = p1; //共享拥有权
p1.use_count() //引用计数
// p1.use_count() == 2
但是shared_ptr会有循环引用的问题,循环依赖主要发生在两个shared_ptr相互引用的情况下,示例代码如下:
struct A;
struct B {
shared_ptr<A> a;
~B() { cout << "B destructor" << endl; }
};
struct A {
shared_ptr<B> b;
~A() { cout << "A destructor" << endl; }
};
int main() {
shared_ptr<A> a = make_shared<A>();
shared_ptr<B> b = make_shared<B>();
a->b = b;
b->a = a;
}
// 在运行程序后,控制台不会打印任何消息,因为AB的析构函数都没有正确的执行
执行结果是A,B的析构函数都不会被调用,出现内存泄露。 分析原因如下:
- A持有B的shared_ptr,B也持有A的shared_ptr
- 每个shared_ptr的引用计数至少为1
- 互相引用导致引用计数永远不为0
- 所以A,B都无法被释放 这就是循环依赖导致的问题。 解决方法就是使用weak_ptr打破循环强引用。用weak_ptr代替一个shared_ptr,weakref不会影响引用计数。
struct A;
struct B {
weak_ptr<A> a;
~B() { cout << "B destructor" << endl; }
};
struct A {
shared_ptr<B> b;
~A() { cout << "A destructor" << endl; }
};
int main() {
shared_ptr<A> a = make_shared<A>();
shared_ptr<B> b = make_shared<B>();
a->b = b;
b->a = a;
}
weak_ptr没有自己的引用计数,它通过与shared_ptr关联来间接引用计数。 具体来说:
- weak_ptr通过构造函数或者assignment与一个shared_ptr关联。
- 此后weak_ptr引用的是这个shared_ptr管理的对象。
- weak_ptr不会增加shared_ptr的引用计数。
- 当最后一个指向对象的shared_ptr被销毁时,对象会被释放,任何指向该对象的weak_ptr都会变为空(expired)。
- 此后调用weak_ptr的lock()会返回空shared_ptr。 所以weak_ptr的生命周期依赖与之关联的那个shared_ptr,它只有一个“是否 expired”的状态来检查强引用的有效性。
这使得weak_ptr可以安全地用于Break循环引用而不影响对象的生命周期。不过weak_ptr本身不增加引用计数。