STL四种智能指针(又名小智买房记)

STL 一共给我们提供了四种智能指针:auto_ptr、unique_ptr、shared_ptr 和 weak_ptr,auto_ptr 是 C++98 提供的解决方案,C+11 已将其摒弃,并提出了 unique_ptr 作为 auto_ptr 替代方案。虽然 auto_ptr 已被摒弃,但在实际项目中仍可使用,但建议使用较新的 unique_ptr,因为 unique_ptr 比 auto_ptr 更加安全,后文会详细叙述。shared_ptr 和 weak_ptr 则是 C+11 从准标准库 Boost 中引入的两种智能指针。

在详细的了解这四种智能指针之前呢,我想让大家对这四种智能指针,有个概念,听完接下来这个故事会有助于你对接下来知识的了解。

我叫小智(你们也可以叫auto_ptr),经过我辛勤的劳动之后,我终于获得了自己的房子(对象),本来这一切都是这么的美好,可是这个时候来了一名流氓(其他智能指针),由于我的房子没有房产证(对象的释放所有权),这个流氓说这个房子是他的,然后住进了房子,不久之后就把房子砸了(释放)。于是小智(智能指针)我崩溃了。

我叫小小智(你们也可以叫unique_ptr),经过我辛勤的劳动之后,我终于又获得了自己的房子,吸取了我爸的经验,我拿到了房产证(对象的释放所有权)。有了房产证了之后,即使其他人看上了我的房子,如果他想住进来都需要先获得我的房产证。只要有房产证我就可以对我的房子干任何事情,甚至把房子砸了。但是房产证只有一份。所以不可能一份房产证砸两遍。

我叫小小小智(你们也可以叫shared_ptr),经过我辛勤的劳动之后,我终于又获得了自己的房子,这会我机智的我把房子出租了出去。出租给了路人甲乙丙丁,收房租,我真是个天才。为了方便计数,每当一次我的房子住进一个人,我就在我的小本本上加上一笔,每有一个人不租了我就会在我的小本本上划掉一笔(计数器计数)。这个时候如果我要拆掉房子,就必须等所有人都不租了,才能拆掉(释放资源)。

我叫小小小小智(你们也可以叫weak_ptr),我是小小小智的助手,专门负责解决下面这样一类问题。小小小智住进了一个和他一样的朋友家里,小小小智的朋友住进了小小小智的房子里。突然小小小智和朋友出国了打算把房子卖了,小本本就在出租屋子里,于是在租客在小本本上自己把名字划掉。但是没有划掉自己和朋友的名字。于是就把这件事交给助手做。助手将他自己和朋友的名字划掉。

auto_ptr

unique_ptr

shared_ptr

 weak_ptr

auto_ptr

auto_ptr 同样是 STL 智能指针家族的成员之一,由 C++98 引入,定义在头文件<memory>。其功能和用法类似于 unique_ptr,由 new expression 获得对象,在 auto_ptr 对象销毁时,他所管理的对象也会自动被 delete 掉。

auto_ptr 从 C++98 使用至今,为何从 C++11 开始,引入unique_ptr 来替代 auto_ptr 呢?

(1)基于安全考虑

auto_ptr< string> ps (new string ("I reigned lonely as a cloud.”);
auto_ptr<string> vocation; 
vocaticn = ps;

上述赋值语句将完成什么工作呢?如果 ps 和 vocation 是常规指针,则两个指针将指向同一个 string 对象。这是不能接受的,因为程序将试图删除同一个对象两次,一次是 ps 过期时,另一次是 vocation 过期时。要避免这种问题,方法有多种:
(1)定义陚值运算符,使之执行深复制。这样两个指针将指向不同的对象,其中的一个对象是另一个对象的副本,缺点是浪费空间,所以智能指针都未采用此方案。
(2)建立所有权(ownership)概念。对于特定的对象,只能有一个智能指针可拥有,这样只有拥有对象的智能指针的析构函数会删除该对象。然后让赋值操作转让所有权。这就是用于 auto_ptr 和 unique_ptr 的策略,但 unique_ptr 的策略更严格。
(3)创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数。例如,赋值时,计数将加 1,而指针过期时,计数将减 1,。当减为 0 时才调用 delete。这是 shared_ptr 采用的策略。

当然,同样的策略也适用于复制构造函数,即auto_ptr<string> vocation(ps)时也需要上面的策略。每种方法都有其用途,但为何要摒弃 auto_ptr 呢?

#include <iostream>
#include <string>
#include <memory>
using namespace std;

int main()
{
	auto_ptr<string> films[5] ={
	auto_ptr<string> (new string("Fowl Balls")),
	auto_ptr<string> (new string("Duck Walks")),
	auto_ptr<string> (new string("Chicken Runs")),
	auto_ptr<string> (new string("Turkey Errors")),
	auto_ptr<string> (new string("Goose Eggs"))
	};
    auto_ptr<string> pwin;
    pwin = films[2]; // films[2] loses ownership. 将所有权从films[2]转让给pwin,此时films[2]不再引用该字符串从而变成空指针

	cout << "The nominees for best avian baseballl film are\n";
	for(int i = 0; i < 5; ++i)
	{
		cout << *films[i] << endl;
	}
 	cout << "The winner is " << *pwin << endl;
	return 0;
}

运行下发现程序崩溃了,原因在上面注释已经说的很清楚,films[2] 已经是空指针了,下面输出访问空指针当然会崩溃了。但这里如果把 auto_ptr 换成 shared_ptr 或 unique_ptr 后,程序就不会崩溃,原因如下:

使用 shared_ptr 时运行正常,因为 shared_ptr 采用引用计数,pwin 和films[2] 都指向同一块内存,在释放空间时因为事先要判断引用计数值的大小因此不会出现多次删除一个对象的错误。

使用 unique_ptr 时编译出错,与 auto_ptr 一样,unique_ptr 也采用所有权模型,但在使用 unique_ptr 时,程序不会等到运行阶段崩溃,而在编译期因下述代码行出现错误:

unique_ptr<string> pwin;
pwin = films[2]; 					//films[2] loses ownership

指导你发现潜在的内存错误。这就是为何要摒弃 auto_ptr 的原因,一句话总结就是:避免因潜在的内存问题导致程序崩溃。

从上面可见,unique_ptr 比 auto_ptr 更加安全,因为 auto_ptr 有拷贝语义,拷贝后原对象变得无效,再次访问原对象时会导致程序崩溃;unique_ptr 则禁止了拷贝语义,但提供了移动语义,即可以使用std::move() 进行控制权限的转移,如下代码所示:
 

unique_ptr<string> upt(new string("lvlv"));
unique_ptr<string> upt1(upt);	//编译出错,已禁止拷贝
unique_ptr<string> upt1=upt;	//编译出错,已禁止拷贝
unique_ptr<string> upt1=std::move(upt);  //控制权限转移

auto_ptr<string> apt(new string("lvlv"));
auto_ptr<string> apt1(apt);	//编译通过
auto_ptr<string> apt1=apt;	//编译通过

这里要注意,在使用std::move将unique_ptr的控制权限转移后,不能够再通过unique_ptr来访问和控制资源了,否则同样会出现程序崩溃。我们可以在使用unique_ptr访问资源前,使用成员函数get()进行判空操作。

unique_ptr<string> upt1=std::move(upt);  							//控制权限转移
if(upt.get()!=nullptr)																//判空操作更安全
{
	//do something
}

(2)unique_ptr 不仅安全,而且灵活
如果unique_ptr 是个临时右值,编译器允许拷贝语义。参考如下代码:

unique_ptr<string> demo(const char * s)
{
    unique_ptr<string> temp (new string (s)); 
    return temp;
}

//假设编写了如下代码:
unique_ptr<string> ps;
ps = demo('Uniquely special");

demo()返回一个临时unique_ptr,然后ps接管了临时对象unique_ptr所管理的资源,而返回时临时的 unique_ptr 被销毁,也就是说没有机会使用 unique_ptr 来访问无效的数据,换句话来说,这种赋值是不会出现任何问题的,即没有理由禁止这种赋值。实际上,编译器确实允许这种赋值。相对于auto_ptr任何情况下都允许拷贝语义,这正是unique_ptr更加灵活聪明的地方。

(3)扩展 auto_ptr 不能完成的功能
(a)unique_ptr 可放在容器中,弥补了 auto_ptr 不能作为容器元素的缺点。
 

//方式一:
vector<unique_ptr<string>> vs { new string{“Doug”}, new string{“Adams”} };  

//方式二:
vector<unique_ptr<string>>v;  
unique_ptr<string> p1(new string("abc"));  

(b)管理动态数组,因为 unique_ptr 有 unique_ptr<X[]> 重载版本,销毁动态对象时调用 delete[]。

unique_ptr<int[]> p (new int[3]{1,2,3});  
p[0] = 0;// 重载了operator[]

(c)自定义资源删除操作(Deleter)。unique_ptr 默认的资源删除操作是 delete/delete[],若需要,可以进行自定义:

void end_connection(connection *p) { disconnect(*p); } //资源清理函数  

//资源清理器的“类型” 
unique_ptr<connection, decltype(end_connection)*> p(&c, end_connection);// 传入函数名,会自动转换为函数指针  
综上所述,基于 unique_ptr 的安全性和扩充的功能,unique_ptr 成功的将 auto_ptr 取而代之。

 

unique_ptr

unique_ptr 由 C++11 引入,旨在替代不安全的 auto_ptr。unique_ptr 是一种定义在头文件<memory>中的智能指针。它持有对对象的独有权——两个unique_ptr不能指向一个对象,即 unique_ptr 不共享它所管理的对象。它无法复制到其他 unique_ptr,无法通过值传递到函数,也无法用于需要副本的任何标准模板库 (STL)算法。只能移动 unique_ptr,即对资源管理权限可以实现转移。这意味着,内存资源所有权可以转移到另一个 unique_ptr,并且原始 unique_ptr 不再拥有此资源。实际使用中,建议将对象限制为由一个所有者所有,因为多个所有权会使程序逻辑变得复杂。因此,当需要智能指针用于存 C++ 对象时,可使用 unique_ptr,构造 unique_ptr 时,可使用 make_unique Helper 函数。

下图演示了两个 unique_ptr 实例之间的所有权转换。

unique_ptr 与原始指针一样有效,并可用于 STL 容器。将 unique_ptr 实例添加到 STL 容器运行效率很高,因为通过 unique_ptr 的移动构造函数,不再需要进行复制操作。unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过 reset 方法重新指定、通过 release 方法释放所有权、通过移动语义转移所有权,unique_ptr 还可能没有对象,这种情况被称为 empty。

//智能指针的创建  
unique_ptr<int> u_i; //创建空智能指针
u_i.reset(new int(3)); //"绑定”动态对象  
unique_ptr<int> u_i2(new int(4));//创建时指定动态对象
unique_ptr<T,D> u(d);	//创建空unique_ptr,执行类型为T的对象,用类型为D的对象d来替代默认的删除器delete

//所有权的变化  
int *p_i = u_i2.release(); //释放所有权  
unique_ptr<string> u_s(new string("abc"));  
unique_ptr<string> u_s2 = std::move(u_s); //所有权转移(通过移动语义),u_s所有权转移后,变成“空指针” 
u_s2.reset(u_s.release());//所有权转移
u_s2=nullptr;//显式销毁所指对象,同时智能指针变为空指针。与u_s2.reset()等价

shared_ptr

 

 weak_ptr


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值