C++智能指针

share_ptr / make_shared

ref: C++——智能指针

智能指针

why

裸指针很危险,忘记释放很容易造成内存泄漏。c++不能完全支持自动垃圾回收,但是c++11添加了智能指针实现堆内存的自动回收。

how

使用代理模式,将裸指针封装起来,构造函数里初始化,析构函数中释放。智能指针本质上是对象

shared_ptr

shared_ptr指针指向的堆内存可以同其他shared_ptr共享。

shared_ptr支持的操作:

shared_ptr<T> p;		// 空智能指针,可以指向类型为 T 的对象
*p;						// 解引用
p->mem;					// 等价于 (*p).mem
p.get();				// 返回 p 保存的指针
p.swap(q); 				// 交换p和q中的指针

make_shared<T>(args);	// 返回shared_ptr,指向一个动态分配的类型为T的对象,使用args初始化对象
shared_ptr<T>p(q);		// p是shared_ptr q的拷贝,q中的计数器递增
p = q;					// p计数器递减,q计数器递增
p.use_count();			// p共享智能指针的数量
p.unique();				// 若p.use_count()为1,返回true,否则false

接受指针参数的智能指针的构造函数是explicit修饰的,因此,不能将一个内置指针隐式转换为一个智能指针,必须使用直接初始化形式来初始化一个智能指针

引用计数

shared_ptr 支持安全共享的原理在于内部使用了“引用计数”

如果发生拷贝赋值,引用计数就增加,而发生析构销毁的时候,引用计数就减少。当引用计数减少到 0,即没有任何人使用这个指针的时候,会调用 delete 释放内存。

注意:引用计数存在循环引用的问题。想要解决,需要使用 weak_ptr。

线程安全问题

  1. 引用计数的加减操作是否线程安全?
    安全,因为是原子操作
  2. shared_ptr指向的对象是否线程安全?
    不安全
    当智能指针发生拷贝的时候,会先拷贝智能指针,再拷贝对象,这两个操作并不是原子的,所以不安全。

make_shared

使用shared_ptr直接创建智能指针存在内存泄露的风险。

auto px = shared_ptr<int>(new int(100));

智能指针初始化包括两个分配过程:

  1. new int 申请内存
  2. 为shared_ptr控制信息分配内存

这两个过程不是原子的,如果1成功,2失败很可能造成内存泄漏。

make_shared的做法是一次性申请一块比对象大一点的内存,多出来那块用来存放控制信息,避免了shared_ptr带来的问题。

auto p = make_shared<int>(100);

make_shared并不是完美的:

因为当强引用计数为0时,会释放引用的对象的内存,但是只有当弱引用计数为0时,才释放引用计数所占用的内存。
现在make_shared申请的内存中同时存放对象数据和引用计数,所以只有当强/弱引用都为0时,才能释放make_shared申请的一整块内存。造成对象数据占用的内存无法得到及时的释放。

释放问题

如果使用智能指针,即使程序块过早结束,智能指针类也能确保在内存不再需要时将其释放。例如:

void f(){
    shared_ptr<int> sp(new int(42));
	// 这里代码发生了一个异常,且未被f()捕获
} // 函数结束时 shared_ptr 自动释放内存。

unique_ptr

某个时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时,它所指向的对象也被销毁。

unique_ptr独有的操作:

unique_ptr<T> u1;		// 空unique_ptr,u1会使用delete释放指针
unique_ptr<T,D> u2;		// 空unique_ptr,u2会使用类型为D的可调用对象释放指针
unique_ptr<T,D> u(d);	// 空unique_ptr,指向T类型,用类型为D的对象代替delete

u.release();			// 放弃对指针的控制权,返回指针,u置空
u.reset(q);				// 释放u指向的对象,并指向q

因为unique_ptr的对象是独享的,所以不能拷贝或赋值,但可以有所有权的转移:

  1. move

    auto ptr1 = make_unique<int>(42); // 工厂函数创建智能指针
    auto ptr2 = std::move(ptr1); // 使用move()转移所有权,ptr1变成空指针
    
  2. release / reset

    // p1 -> p2
    unique_ptr<string> p2(p1.release());
    // p2 -> p3
    p2.reset(p3.release());  // 释放了原来指向的内存
    

weak_ptr

weak_ptr专门为打破循环引用而设计
因为weak_ptr只观察指针,不会增加引用计数,但在需要的时候,可以调用成员函数 lock(),获取 shared_ptr(强引用)

weak_ptr支持的操作:

weak_ptr<T> w;			// 空weak_ptr
weak_ptr<T> w(sp);		// 和shared_ptr指向相同的对象

w = p;					// 同上
w.reset();				// 置空
w.use_count();			// 共享的shared_ptr的数量
w.expired();			// w.use_count() == 0 ? true : false;
w.lock();				// w.expired() ? nullptr(shared_ptr) : shared_ptr(point to w)

你可能感兴趣的:(C++,c++)