C++ 动态内存和智能指针shared_ptr

引言

在C++中,最麻烦的就是内存管理。动态内存的使用很容易出问题,因为确保在正确的时间释放内存是非常困难的事情。

有时,我们会忘记释放内存,这种情况就会出现内存泄漏;有时在指针还在引用内存的情况释放了它,这种情况就会出现引用非法内存的指针。

为了能更加安全的使用动态内存,C11提供了两种智能指针类型管理动态对象 。

智能指针

  • shared_ptr 允许多个指针指向一个对象。
  • unique_ptr 则独占所指向的对象。
  • weak_ptr 弱引用。

shared_ptr 和 unique_ptr都支持的操作

操作功能
shared_ptr<T> sp空智能指针,可以指向类型为T的对象
unique_ptr<T> sp同上
*p解引用p,获得它指向的对象
p.get()返回p中保存的指针。要小心使用,若智能指针释放了对象,返回的指针所指的对象也就消失了
swap(p, q)交换p和q的指针
p.swap(q)同上

shared_ptr独有的操作

操作功能
make_shared<T> (args)返回了一个动态分配的类型为T的对象。使用args初始化该对象
shared_ptr<T> p(q)p是shared_ptr q的拷贝:此操作会递增q中的计数器。q中的指针必须转换为*T
p.unique()若p.use_count()为1,返回true;否则返回false;
p.use_count()返回与p共享对象的智能指针数量:可能很慢,主要用于调试

make_shared也定义在头文件memory中

shared_ptr 的拷贝和赋值

auto p = make_shared<int>(42);
auto q(p);

每个shared_ptr都有一个关联的计数器,通常称其为引用计数。

无论何时,我们拷贝一个shared_ptr,计数器都会递增,例如,当用一个shared_ptr初始化另一shared_ptr,或者让它作为参数传一个函数,以及做函数的返回值(用的是拷贝传参)。它所关联的计数器都会递增。

但我们给shared_ptr赋予一个新值,或者shared_ptr被销毁。计数器就会递减。

一旦一个shared_ptr的计数器变为0。它就会自动**释放自己所管理的对象。**这一特性使得动态内存的使用变得简单。

auto r = make_shared<int>(42);
r = q; //赋予新值。计数器减一 

shared_ptr使用场景

现在我们要使用一个函数,它返回一个shared_ptr,指向一个Foo类型的动态分配的对象。

shared_ptr<Foo> factory(T arg){
	return make_shared<Foo>(arg);
}

由于factory返回一个shared_ptr,所以我们可以确保它分配的对象会在恰当的时刻被释放掉。例如

void use_factory(T arg){
	shared_ptr<Foo> p = factory(arg);
}

由于p是函数的局部变量,在use_factroy结束时,它将会被销毁。当p被销毁,将递减其计数器并检查它是否为0,如果p是唯一引用factory返回的内存的对象,那么该对象会被释放,否则继续保留。

当多其他地方需要这个shared_ptr指向这个内存,那么它就不会被释放掉。

void use_factory(T arg){
	shared_ptr<Foo> p = factory(arg);
	return p; //当我们返回p,引用计数进行了增量操作
}//p离开了作用域,但是它指向的内存没有被释放掉

shared_ptr和new混合使用

shared_ptr<double> p1 = new int(43);	//错误:必须使用直接初始化式
shared_ptr<int> p2(new int(43));		//正确:使用了直接初始化式

shared_ptr不能支持隐式的转换(从内置指针到智能指针的转换),因此第一条的初始化语句是错的。

出于同种原因。下面的语句也是错的。

shared_ptr<int> clone(int p){
	return new int(p); //错误
}

要改成

shared_ptr<int>
clone(int p){
	return shared_ptr<int>(new int(p));
}

默认情况下,一个用来初始化智能指针的普通指针必须指向动态内存,因为智能指针默认使用delete来释放自己的内存。

定义和改变shared_ptr的方法功能
shared_ptr<T> p(q)p 管理内置指针q所指向的对象;q必须指向new分配的内存,且能够转化为T类型
shared_ptr<T> p(u)p从unique_ptr u那里接管了对象的所有权,将u设置为空
shared_ptr<T> p(q, d)p从q那里接管了对象的所有权,并使用d代替delete操作
p.reset()若p是唯一指向对象的shared_ptr,reset将会释放此时的对象
p.reset(q)若传递了可选的参数内置指针q ,会令p指向q,否则将p置为空
p.reset(q, d)若传递了参数d,将会调用d,将会使用d代替delete操作来释放q

注意事项

不要使用独立的智能指针指向同一个对象

这里使用p.get(), 返回p保存的指针给q,然后再某个函数体 又将另一个独立的shard_ptr保存

而函数体内的shared_ptr的count和函数体外的shared_ptr并不是同一个计数器,函数结束后,将指针指向的对象释放掉,那么shared_ptr p所指向的也释放掉,从而导致了错误。

shared_ptr<int> p(new(int(43));
int *q = p.get();
void func(){
	shared_ptr<int>(q);
}
int foo = *p;//error

智能指针和异常

在使用智能指针,函数遇到了异常。

void f(){
	shared_ptr<int> p(new int (43));
	//函数体发生了异常,并且没有被函数f捕获。
	//在函数体结束时,通过shared_ptr释放了内存。
}

在使用内置指针,函数遇到了异常

void f(){
	int *p = new int(43);
	//函数发生了异常
	delete(p);//这时候由于函数发生了异常,导致new出来的内存一直都没法释放掉。
}
智能指针在哑类的使用

在标准库内有许多C++的类都定义了析构函数,负责清理对象使用的资源。但是不是所有的类都有这样良好的定义。

对于这种使用了资源,但是没有定义析构函数来释放资源的类,可能会遇到和使用动态内存同样的错误。

struct destination;				//表示我们在连接什么
struct connection;				//表示连接所需要的信息
connection connect("destination");	//打开连接
void disconnect(connection);			//关闭给定的连接
void  f(destination &d){					
	connection c = connect(&d);		//使用连接,但是忘记关了
}

但是由于connection没有析构函数,我们可以使用shared_ptr来保证connection正确关闭。

默认情况下, shared_ptr指向的是动态内存,当一个shared_ptr被销毁时, 它默认给它管理的对象进行delete操作,而在这个案例中,我们通过修改delete操作,来实现对该类对象的智能管理。

void end_connection(connection *p ){disconnect(*p);}

替代之前的函数

void f(){
	connection c = connect(&d);
	shared_ptr<connection> p(&c, end_connection);
	//使用连接
	//当f退出,即便是异常退出,connection都会自动关闭。
	//用lambda来写
	shared_ptr<connection> p(&c, [](connection *p){ disconnect(p)});
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值