c++11 智能指针

智能指针

c++程序设计中经常会用堆内存,程序员要自己管理内存的申请和释放。使用原始指针,容易造成堆内存泄漏(忘记释放),二次释放;使用智能指针能更好的管理堆内存。

c++中四个智能指针:auto_ptr,unique_ptr,shared_ptr,weak_ptr,auto_ptr已被c++11弃用
头文件 #include <memory>
使用时,调用智能指针自身带的函数用 . ; 调用所指向对象的成员用->

1. shared_ptr(共享的智能指针)

1)定义:允许多个指针指向同一个对象,内部维护一个计数器,无论何时拷贝一个shared_ptr,计数器都会递增,当指向该对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁该对象。

shared_ptr类,类似vector ,智能指针也是模板,当我们创建一个智能指针时,必须提供额外的信息:指针可以指向的类型。与vector一样,在尖括号内给出类型,之后是所定义的这种智能指针的名字。
use_count() 有多少个指针指向当前对象,即引用计数

2)基本用法:

  • 初始化 优先使用make_shared
// 1 通过构造函数、std::shared_ptr辅助函数、reset方法来初始化
std::shared_ptr<int> p1(new int(1));
std::shared_ptr p2 = p1;
std::shared_ptr<int> p3;
p3.reset(new int(1)); // 与赋值类似,reset会更新引用计数,p3指向一个新对象,p3原来指向的对象计数-1

if (p3) {
	cout << "p3 not null" << endl;
}

// 2 应该优先使用make_shared来构造智能指针,更高效
auto p1 = make_shared<int>(100);
// 相当于
shared_ptr<int> sp1(new int(100));

// 不能将原始指针赋值给智能指针
std::shared_ptr<int> p = new int(1); // error
  • 获取原始指针
std::shared_ptr<int> ptr(new int(1));
int* p = ptr.get(); // 返回ptr中保存的指针,要小心使用,若智能指针释放了对象,返回的指针所指向的对象也就消失了
  • 指定删除器
    智能指针初始化可以指定删除器
    当我们使用shared_ptr管理动态数组时,需要指定删除器,默认删除器不支持数组对象
#include <memory>
void delete_ptr(int* p) {
	delete p;
}
int main(){
	std::shared_ptr<int> p(new int(1), delete_ptr);
}

// 删除器也可以是lamda表达式
std::shared_ptr<int> p(new int[10], [](int* p){delete [] p;}); 

3)使用注意事项

  • 不要用一个原始指针初始化多个shared_ptr
// 如果用同一个原始指针分别初始化了多个shared_ptr,这些shared_ptr指向同一块内存,并不知道相互的存在,则会在引用减为0的时候,分别析构原始指针,造成多次析构
int* p = new int;
shared_ptr<int> p1(p);
shared_ptr<int> p2(p); // error 
  • 不要在函数实参中创建shared_ptr
    c++函数参数的计算顺序在不同的编译器不同的约定下可能是不一样的,顺序不同会引起一些异常
  • 通过shared_from_this() 返回this指针
    不要将this指针作为shared_ptr返回出来,因为this指针本质上是一个裸指针,可能会导致重复析构
#include <iostream>
#include <memory>
using namespace std;

class A {
public:
  shared_ptr<A> get_self() {
	return shared_ptr<A>(this); // 这样有问题
  }
  ~A() { cout << "Destruction A" << endl; }
};
int main() {
	shared_ptr<A> sp1(new A);
	shared_ptr<A> sp2 = sp1->get_self();
	return 0;
}
//  用同一个指针this构造了两个智能指针,他们之间没有联系,离开作用域后this会被两个智能指针各自析构,导致重复析构的错误

// 正确做法
class A:public std::enable_shared_from_this<A> {
public:
	shared_ptr<A> get_self() {
		return shared_from_this();
	}
	~A(){}
}
  • 避免循环引用 (循环引用会导致内存泄漏)
#include <iostream>
#include <memory>
using namespace std;
class A;
class B;

class A {
public:
	shared_ptr<B> bptr;
	~A(){cout << "A is delete" << endl;}
};

class B {
public:
	shared_ptr<A> aptr;
	~B(){cout << "B is delete" << endl;}
};

int main() {
	{
		shared_ptr<A> ap(new A);
		shared_ptr<B> bp(new B);
		ap->bptr = bp;
		bp->aptr = ap;
	}
	cout << "main leave" << endl;
	return 0;
}

如下图所示,循环引用导致对象ap和bp的引用计数为2,在离开作用域后,ap、bp指针被释放,对象的引用计数变为1,不会减为0。A中通过bptr持有了B,A要释放要B先释放。
看图理解会容易些。
在这里插入图片描述
4)多线程使用shared_ptr
shared_ptr的引用计数本身是安全且无锁的,对象的读写不是,因为shared_ptr有两个数据成员,读写操作不能原子化。

shared_ptr的线程安全级别:

  • 一个shared_ptr对象实体可被多个线程同时读取
  • 两个shared_ptr对象实体可以被两个线程同时写入
  • 如果多个线程同时读写同一个shared_ptr对象,需要加锁

以上是shared_ptr对象本身的线程安全级别,不是它管理的对象(即指向的内存空间)的线程安全级别。
shared_ptr 包含两个成员,一个是指向Foo的指针ptr,另一个是ref_count指针,指向堆上的引用计数对象;这两个数据成员,读写操作不能原子化,使得多线程读写同一个shared_ptr对象需要加锁
参考:https://blog.csdn.net/solstice/article/details/8547547

2. unique_ptr(独占的智能指针)

独占所指向的对象,某个时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时,它所指向的对象也被销毁。
不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr,但可以通过函数返回给其他的unique_ptr,或者用std::move转移到其他的unique_ptr
使用std::move转移后,该unique_ptr为nullptr

unique_ptr<T> ptr(new T);
unique_ptr<T> other_ptr = ptr; // error 不允许复制

unique_ptr<T> my_ptr(new T); // 正确
unique_ptr<T> my_other_ptr = std::move(my_ptr); // 正确
unique_ptr<T> ptr = my_ptr; // error 不能复制 

unique_ptr 和 shared_ptr的区别

  • unique_ptr可以指向一个数组
std::unique_ptr<int []> ptr(new int[10]);
ptr[9]=9;
std::shared_ptr<int []> ptr2(new int[10]); // error不可以这样用
  • unique_ptr指定删除器和shared_ptr有区别
std::shared_ptr<int> ptr3(new int(1), [](int *p){delete p;}); // 正确 
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;}); // 正确,需要指定删除器的类型

使用选择:
依据使用场景进行选择
如果希望只有一个智能指针管理资源或者管理数组就用unique_ptr
如果希望多个智能指针管理同一个资源就用shared_ptr

3. weak_ptr(弱引用的智能指针)

1)定义
weak_ptr是一种不控制对象生命周期的智能指针,它指向一个shared_ptr管理的对象。进行该对象内存管理的是那个强引用的shared_ptr,weak_ptr只是提供了对管理对象的一个访问手段。
设计的目的是为配合shared_ptr而引入的一种智能指针来协助shared_ptr工作,它只可以从一个shared_ptr或者另一个weak_ptr对象构造,它的构造和析构不会引起引用计数的增加或减少。
用来解决shared_ptr相互引用时的死锁问题。

weak_ptr没有重载操作符* 和 -> , 在进行资源操作时要用shared_ptr

2)基本用法

  • 通过use_count() 方法获取当前观察资源的引用计数
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
// weak_ptr<int> wp = sp; 也可以这样赋值
cout << wp.use_count() << endl; // 结果1
  • 通过expired()方法判断所观察资源是否已经释放
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
if(wp.expired()) {
	cout << "weak_ptr无效,资源已释放" << endl;
}
  • 通过lock方法获取监视的shared_ptr
    weak_ptr不可以直接操作shared_ptr对象的成员、方法,需要用lock方法获取后,才可以。
class A {
 public:
  A() : a(10) {}
  int a;
};

std::shared_ptr<A> sp(new A);
std::weak_ptr<A> wp;
cout << wp.use_count() << " " << wp.expired() << endl; // 1,0

if (!wp.expired()) {
  std::shared_ptr<A> ptr = wp.lock(); 
  // 因为weak_ptr不能直接操作成员,用lock获取一个shared_ptr来进行操作
  ptr->a = 20;
}
cout << sp->a << endl; // 20

4. 智能指针安全性问题

  • shared_ptr是不是线程安全的?
    引用计数是安全的,多线程下,赋值几次,值就加几次
    不安全:多线程代码操作的是同一个shared_ptr的对象
    安全:多线程代码操作的不是同一个shared_ptr对象

建议传值使用(拷贝一份),不传地址

  • 4
    点赞
  • 79
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值