std::auto_ptr : 已被c++11废弃
std::unique_ptr :独占资源所有权的指针。
std::shared_ptr :共享资源所有权的指针。
std::weak_ptr :共享资源的观察者,需要和 std::shared_ptr 一起使用,不影响资源的生命周期。
对于shared_ptr来说,引用计数是线程安全的,但是数据是线程不安全的,需要内部对数据进行加锁。
当我们想独占资源的所有权时,可以使用std::unique_ptr对资源进行管理,离开该std::unique_ptr对象的作用域时,会自动释放资源。这是很基本的RAII思想(RAII,Resource Acquisition Is Initialization,由c++之父Bjarne Stroustrup提出,即:资源获取即初始化,他说:使用局部对象来管理资源的技术称之为“资源获取即初始化”)。
4.1.1 使用裸指针,需要手动释放
{
int* p = new int(10);
// ...
delete p; // 要记得释放内存
}
4.1.2 使用unique_ptr,自动释放内存
{
std::unique_ptr<int> u_ptr = std::make_unique<int>(10);
// ...
// 离开该作用域会自动释放内存
}
4.1.3 unique_ptr所有权的转移,只支持move转移
{
std::unique_ptr<int> u_ptr = std::make_unique<int>(10);
std::unique_ptr<int> u_ptr1 = u_ptr; // 编译报错, unique_ptr只支持move转移所有权
std::shared_ptr<int> s_ptr = u_ptr; // 编译报错,不支持与shared_ptr混用
std::unique_ptr<int> u_ptr2 = std::move(u_ptr);
}
4.1.4 unique_ptr 还可以指向数组
{
std::unique_ptr<int[]> u_ptr = std::make_unique<int[]>(10);
for (int i = 0; i < 10; i++) {
u_ptr [i] = i;
}
for (int i = 0; i < 10; i++) {
std::cout << u_ptr [i] << std::endl;
}
}
本质是对资源做引用计数,当引用计数为0时,释放该资源。
4.2.1 使用shared_ptr,自动释放内存
{
std::shared_ptr<int> p = new int(1); // 错误,不能将裸指针直接赋值为智能指针
std::shared_ptr<int> s_ptr = std::make_shared<int>(10);
assert(s_ptr.use_count() == 1); // 此时,s_ptr的引用计数为1
{
std::shared_ptr<int> s_ptr1 = s_ptr;
assert(s_ptr.use_count() == 1); // 此时,s_ptr的引用计数为2
}
assert(s_ptr.use_count() == 1); // 离开s_ptr1 的作用域,引用计数减1
}
// 引用计数为0,释放该资源
4.2.2 shared_ptr也可以指向数组
{
std::shared_ptr<int[]> s_ptr(new int[10]);
// std::shared_ptr u_ptr = std::make_shared(10); c++20才支持
for (int i = 0; i < 10; i++) {
s_ptr[i] = i;
}
for (int i = 0; i < 10; i++) {
std::cout << s_ptr[i] << std::endl;
}
}
4.2.3 当需要获取原始指针时,可以通过get方法。不过,谨慎使用get方法。
{
int main()
{
int *ptmp = new int(10);
std::shared_ptr<int> s_ptr(ptmp); // 裸指针委托智能指针进行管理
//std::shared_ptr s_ptr = std::make_shared(10);
int* ptr = s_ptr.get();
std::cout << ptmp << ", " << ptr << std::endl;
return 0;
}
}
4.2.4 通过 shared_from_this() 返回 this 指针
不要将this指针作为shared_ptr 返回出来,因为 this 指针本质上市裸指针,可能会导致多次析构。
#include
#include
using namespace std;
class A {
public:
shared_ptr<A> GetSelf(){
return shared_ptr<A>(this);
}
~A(){
cout << "Destructor A" << endl;
}
}
int main()
{
shared_ptr<A> s_ptrA(new A);
shared_ptr<B> s_ptrB = s_ptrA->GetSelf();
return 0;
}
运行结果会调用两次析构函数。因为用一个this指针构造了两个指针指针,而这两个智能指针是没有关系的。所以,在程序结束时,都会调用各自的析构函数,导致重复析构。
正确的做法是:让需要返回的类共有继承 std::enable_shared_from_this 类,然后使用基类的成员函数shared_from_this() 来返回目标类 this 的 shared_ptr。
#include
#include
using namespace std;
class A : public std::enable_shared_from_this<A> {
public:
shared_ptr<A> GetSelf(){
return shared_from_this();
}
~A(){
cout << "Destructor A" << endl;
}
}
int main()
{
shared_ptr<A> s_ptrA(new A);
shared_ptr<B> s_ptrB = s_ptrA->GetSelf();
return 0;
}
4.2.5 循环引用,导致内存泄漏
#include
#include
using namespace std;
class A;
class B;
class A {
public: std::shared_ptr<B> bptr;
~A() {
cout << "A is deleted" << endl;
}
};
class B {
public: std::shared_ptr<A> aptr;
// public: std::weak_ptr aptr; // 解决循环引用问题
~B() {cout << "B is deleted" << endl; }
};
int main() {
{
std::shared_ptr<A> ap(new A);
std::shared_ptr<B> bp(new B);
ap->bptr = bp;
bp->aptr = ap;
}
cout<< "main leave" << endl; // 循环引用导致ap bp退出了作用域都没有析构
return 0;
}
说明:循环引用导致ap和bp的引用技术都是2,在离开作用域后,各自的引用记数减1,并没有减为0。导致两个智能指针都没有被析构,导致内存泄漏。
解决方法:把A和B任何一个成员变量改为weak_ptr
shared_ptr内部有两个指针,一个指向目标对象,另一个指向控制块,控制块包含引用计数、弱计数和删除器和其他数据。
5.1 指定删除器
在使用shared_ptr管理非new对象或者没有析构函数时,需要为其指定删除器。
//1-3-1-delete
#include
#include
using namespace std;
void DeleteIntPtr(int *p) {
cout << "call DeleteIntPtr" << endl;
delete p;
}
int main() {
std::shared_ptr<int> p(new int(1), DeleteIntPtr);
std::shared_ptr<int> p(new int(1), [](int *p) { delete p; }); //使用lambda表达式
// 注意 在指定 unique_ptr 的删除器时,需要指明删除器的类型
std::unique_ptr<int> ptr4(new int(1), [](int *p){delete p;}); // 错误
std::unique_ptr<int, void(*)(int*)> ptr5(new int(1), [](int *p){delete p;}); // 正确
return 0;
}
share_ptr虽然已经很好用了,但是有一点share_ptr智能指针还是有内存泄露的情况,当两个对象相互
使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。
weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从
一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。
6.1 基本用法
int main()
{
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
cout << wp.use_count() << endl; //结果讲输出1
// 通过 expired 方法判断所观察对象是否已经被释放
if(wp.expired())
cout << "weak_ptr无效,资源已释放";
else
cout << "weak_ptr有效";
return 0;
}
6.2 通过lock方法获取监视对象的shared_ptr。
举例:比如在多线程情况下
std::weak_ptr<int> gw; // 全局变量
void func()
{
auto sptr = gw.lock();
if (gw.expired()){
cout << "gw无效,资源已释放";
}else{
cout << "gw有效, *spt = " << *spt << endl;
}
}
int main()
{
{
auto sp = std::shared_ptr<int>(100);
gw = sp;
func(); // 还在shared_ptr作用域内,gw有效
}
func(); // 出了作用域,打印:"gw无效,资源已释放";
}
文章参考与<零声教育>的C/C++linux服务期高级架构。