C++_类入门5-智能指针模板类
智能指针是行为类似于指针的类对象,但这种对象还有其他功能。
本节介绍三个可帮助管理动态内存分配的智能指针模板(auto_ptr、unique_ptr和shared_ptr)。
void remodel(std:string & str)
{
std::string * ps = new std::string(str);
...
str = ps;
return;
}
这段代码有缺陷,每当调用时,该函数都分配堆中的内存,但从不回收,从而导致内存泄漏;
但是有解决之道——在return语句前添加下面的语句,以释放分配的内存即可:
delete ps;
然而,但凡涉及“别忘了”的解决方法,很少是最佳的。因为有时候可能忘记了,有时候可能记住了。
即使确实没有忘记,但也可能有问题,如下:
void remodel(std::string & str)
{
std::string * ps = new std::string(str);
...
if (weird_thing())
throw exception();
str = *ps;
delete ps;
return;
}
当出现异常时,delete将不被执行,因此也将导致内存泄漏;
首先分析一下过程,当remodel()函数终止时,本地变量都将从栈内存中删除——因此指针ps占据的内存将被释放。
如果ps指向的内存也被释放,那该多好啊。如果ps有一个析构函数,该析构函数在ps过期时释放它所指向的内存。
因此问题就在于ps是一个常规的指针,不是有析构函数的类对象。如果它是对象,则可以在对象过期时,让它的析构函数删除指向的内存。
这正是智能指针模板背后的思想。
auto_ptr 是C++98提供的解决方案,但被C++11所摒弃;
C++提供了另外两种解决方案unique_ptr和shared_ptr;
=========================================
一、使用智能指针
这三个智能指针模板(auto_ptr、unique_ptr和shared_ptr)都定义了类似指针的对象,可以将new获得的地址(直接或间接)赋给这种对象。
当智能指针过期时,其析构函数将使用delete来释放内存。
因此如果将new返回的地址赋给这些对象,将无需记住稍后释放这些内存:在智能指针过期时,这些内存将自动释放。
要创建智能指针对象,必须包含头文件memory,该文件模板定义。
然后使用通常的模板语法来实例化所需的类型指针。
例如:模板auto_ptr包含如下构造函数:
template <class X> class auto_ptr {
public:
explicit auto_ptr(X*p = 0) throw();
...
};
auto_ptr<double> pd (new double); //pd是auto_ptr to double,代替了double * pd
auto_ptr<string> ps (new string); //ps是auto_ptr to string,代替了 string * ps
其中的new double是new返回的指针,指向新分配的内存块。它是构造函数auto_ptr<double>的参数;
因此,要转换remodel()函数,应按照下面3个步骤进行:
1、包含头文件memory;
2、将指向string的指针替换为指向string的智能指针对象;
3、删除delete语句;
下面是使用auto_ptr修改该函数的结果:
#include<memory>
void remodel(std::string & str)
{
std::auto_ptr<std::string> ps (new std::string(str));
...
if (weird_thing())
throw exception();
str = * ps;
//delete ps; NO LONGER NEEDED
return;
}
注意到智能指针位于std名称空间中,接下来的程序演示了如何使用全部三种指针。
//smrtptrs.cpp -- using three kinds of smart pointers
//requires support of C++11 shared_ptr and unique_ptr #include <iostream>
#include <string>
#include <memory> class Report
{
private:
std::string str;
public:
Report (const std::string s):str(s)
{std::cout<<"Object created!\n";}
~Report() {std::cout<<"Object deleted!\n";}
void comment() const {std::cout<<str<<"\n";}
}; int main()
{
{
std::auto_ptr<Report> ps (new Report("using auto_ptr"));
ps -> comment(); //use -> to invoke a member function
}
{
std::shared_ptr<Report> ps (new Report("using shared_ptr"));
ps -> comment();
}
{
std::unique_ptr<Report> ps (new Report("using unique_ptr"));
ps->comment();
}
return ;
}
所有智能指针类都有一个explicit构造函数,该构造函数将指针作为参数。因此不需要自动将指针转换为智能指针对象://explicit直言的、不隐瞒的;//implicit不言明的、含蓄的
shared_ptr<double> pd;
double *p_reg = new double;
pd = p_reg; // not allowed (implicit conversion)
pd = shared_ptr<double>(p_reg); // allowed (explicit conversion)
shared_ptr<double> pshared = p_reg; //not allowed (implicit conversion)
shared_ptr<double> pshared(p_reg); //allowed (explicit conversion)
由于智能指针模板类的定义方式,所以其在很多方面都非常像常规指针。
例如,如果ps是一个智能指针对象,则可以对它执行解除引用操作(* ps)、用它来访问结构成员(ps->puffIndex)、将它赋给指向相同类型的常规指针。
还可以将智能指针对象赋给另一个同类型的智能指针对象,但将引起一个问题。在后续会讨论。
但在此之前,先说说对全部三种智能指针都应避免的一点:
string vacation("I wandered lonely as a cloud.");
shared_ptr<string> pvac (&vacation); //NO!
pvac过期时,程序将把delete运算符用于非堆内存,这是不对的;
=========================================
二、有关智能指针的注意事项
为何摒弃auto_ptr呢?首先看下下列语句:
auto_ptr<string> ps (new string("I reigned lonely as cloud."));
auto_ptr<string> vocation;
vocation = ps;
这两个指针将指向同一个string对象,这是不能接受的。因为程序将试图删除同一个对象两次——一次是ps过期时,另一次是vocation过期时。
要避免这种问题,有很多方法:
1、定义赋值运算符,使之执行深复制。这样两个指针将指向不同的对象,其中一个对象是另一个对象的副本。
2、建立所有权(ownership)概念,对特定的对象,只能有一个智能指针可拥有它,这样只有拥有对象的智能指针的析构函数会删除该对象。然后,让赋值操作转让所有权。这就是用于auto_ptr和unique_ptr的策略,但unique_ptr的策略更严格。
3、创建智能更高的指针,跟踪引用特定对象的智能指针数。这称之为引用计数(reference counting)。例如,赋值时,计数将加1,指针过期时,计数将减1。仅当最后一个指针过期时,才调用delete。这是shared_ptr采用的策略。
接下来是一个不适合使用auto_ptr的例子:
//fowl.cpp -- auto_ptr a poor choice
#include <iostream>
#include <string>
#include <memory> int main()
{
using namespace std;
auto_ptr<string> films[] =
{
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("Goose Eggs")),
auto_ptr<string> (new string("Turkey Errors"))
};
auto_ptr<string> pwin;
pwin = films[]; cout << "The nominees for best avian basketball film are\n";
for (int i = ; i < ; i++)
cout<<*films[i]<<endl;
cout << "The winner is "<< * pwin<<"!\n";
cin.get();
return ;
}
下面是程序的输出:
The nominees for best avian baseball film are
Fowl Balls
Duck Walks
Segmentation fault(core dumped)
消息core dumped 表明,错误地使用auto_ptr可能导致问题。问题在于,下面的语句将所有权从film[2]转让给pwin:
pwn = films[2];
当film[2]不再引用该字符串,在auto_ptr放弃对象的所有权后,但有可能会使用它来访问该对象。
当程序打印film[2]指向的字符串时,却发现这是一个空指针,这显然是讨厌的意外。
如果程序使用shared_ptr代替auto_ptr,则程序将正常运行。这次pwinhefilms[2]指向同一个对象,而引用计数从1增加到2。
在程序末尾,后声明的pwin将首先调用其析构函数,该析构函数将引用计数降低到1。
然后,shared_ptr数组的成员被释放,对film[2]调用析构函数,将引用计数降低到0,并释放以前分配的空间。
因此使用shared_ptr,可以保证程序正常运行;使用auto_ptr会导致程序在运行阶段崩溃。
如果使用unique_ptr,结果将如何呢?与auto_ptr一样,unique_ptr也采用所有权模型。但使用unique_ptr时,程序不会等到运行阶段崩溃,而在编译器因下述代码行出现错误:
pwin = films[2];
=========================================
三、unique_ptr为何优于auto_ptr
auto_ptr<string> p1 (new string("auto")); //#1
auto_ptr<string> p2; //#2
p2 =p1; //#3
在语句#3中,p2接管string对象的所有权后,p1的所有权将被剥夺。
这是件好事,可以防止p1和p2的析构函数试图删除同一个对象。
但是如果随后程序试图使用p1,这将是件坏事,因为p1不再指向有效数据。
下面来看使用unique_ptr的情况:
unique_ptr<string> p3 (new string (''auto')); //#4
unique_ptr<string> p4; //#5
p4 = p3; //#6
编译器认为语句#6非法,避免了p3不再指向有效数据的问题。
因此,unique_ptr比auto_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被销毁。
这样做没有问题,因为ps拥有了string对象的所有权。
这里做的另一个好处就是,demo返回的临时unique_ptr被销毁。也就没有机会来使用它返回无效数据。
换句话说,没有理由禁止这种赋值,神奇的是,编译器确实允许这种赋值。
总之,程序试图将unique_ptr赋值给另一个时,如果unique_ptr是一个临时右值的话,编译器允许这样做。
如果源unique_ptr将存在一段时间,编译器将禁止这样做。
//证明unique_ptr比auto_ptr好用
using namespace std;
unique_ptr<string> pu1 (new string "Hi ho!");
unique_ptr<string> pu2;
pu2 = pu1; //#1 not allowed
unique_ptr<string> pu3;
pu3 = unique_ptr<string>(new string "Yo!"); //#2 allowed
语句1将留下悬挂的unique_ptr指针,这将导致危害。
语句2不会留下悬挂的unique_ptr指针,因为它调用unique_ptr的构造函数,该构造函数创建的临时对象在其所有权转让给pu3后,会被销毁。
这种随情况而异的行为表明,unique_ptr优于auto_ptr。
//下面这段更加晦涩
如果我们确实想执行类型#1的语句那样的操作的话。
仅当以非智能的方式使用遗弃的智能指针,这种赋值才不安全。
要安全地重用这种指针,可给它赋新值。
C++有一个标准库函数std::move(),让您能够将一个unique_ptr赋给另一个。
using namespace std;
unique_ptr<string> ps1, ps2;
ps1 = demo("Uniquely special");
ps2 = move(ps1); //允许赋值
ps1 = demo(" and more");
cout<< *ps2 << *ps1 <<endl;
这段代码中用到了移动构造函数和右值引用。
相对于auto_ptr,unique_ptr还有另一个优点。它有一个可用于数组的变体。别忘了,必须将delete和new配对,将delete[]和new[]配对。
而模板auto_ptr使用的是delete,而不是delete [],因此只能与new一起使用,而不能与new []一起使用。
但是unique_ptr有使用new[]和delete[]的版本。
=========================================
四、选择智能指针
应该如何选用只能指针?
如果程序要使用多个指向同一个对象的指针,应选择share_ptr。
这样的情况包括:有一个数组,并使用一些辅助指针来标识特定的元素,如最大的元素和最小的元素;
两个对象都包含指向第三个对象的指针;STL容器包含指针。
很多STL算法都支持赋值和复制操作。这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出警告)和auto_ptr(行为不确定)。
如果您的编译器没有支持shared_ptr,可使用Boost库提供的shared_ptr。
如果程序不需要多个指向同一个对象的指针,则可以使用unique_ptr。
如果函数使用new分配内存,并返回指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。
这样所有权将转让给接受返回值的unique_ptr,而该指针将负责调用delete。
如果编译器没有提供unique_ptr,可考虑使用BOOST库提供的scoped_ptr,它与unique_ptr类似。
C++_类入门5-智能指针模板类的更多相关文章
- 《C++ Primer Plus》16.2 智能指针模板类
智能指针是行为类似于指针的类对象,单这种对象还有其他功能.本节介绍三个可帮助管理动态内存分配的智能指针类.先来看看需要哪些功能以及这些功能是如何实现的.请看下面的函数:void remodel(std ...
- C++智能指针管理类
1.程序员明确的进行内存释放 对于c++程序员,最头脑的莫过于对动态分配的内存进行管理了.c++在堆上分配的内存,需要程序员负责对分配的内存进行释放.但有时内存的释放看起来并不件很轻松的事,如下程序 ...
- [cocos2dx注意事项014]一个用于cocos2dx对象智能指针模板
现在,C++有许多实现智能指针,一个更无所谓.哈. 这种智能指针是专为cocos2dx 2.2.x自定义.主要的易用性,同时必须遵循现有的cocos2dx内存管理.特殊实现这样的智能指针.无需在使用时 ...
- VB6/VBA中跟踪鼠标移出窗体控件事件(类模块成员函数指针CHooker类应用)
一.关于起因 前几天发了一篇博文,是关于获取VB类模块成员函数指针的内容(http://www.cnblogs.com/alexywt/p/5880993.html):今天我就发一下我的应用实例. V ...
- 线性表seqList类及其父类list,模板类
seqList模板类,线性表代码 # include "list.h" //代码清单2-2 顺序表类的定义和实现 // The Definition of seqList temp ...
- 根据OSG中的ref_ptr和Reference简化的智能指针
main.cpp测试代码 #include "TestSmartPointer" void fun() { SP<TestSmartPointer> sp1=new T ...
- C++笔记(11) 智能指针
1. 设计思想 智能指针是行为类似于指针的类对象,但这种对象还有其他功能.首先,看下面的函数: void remodel(std::string & str) { std::string * ...
- STL模板_智能指针概念
一.智能指针1.类类型对象,在其内部封装了一个普通指针.当智能指针对象因离开作用域而被析构时,其析构函数被执行,通过其内部封装的普通指针,销毁该指针的目标对象,避免内存泄露.2.为了表现出和普通指针一 ...
- [翻译]将智能指针用于C++的类成员
http://stackoverflow.com/questions/15648844/using-smart-pointers-for-class-members Question: I'm hav ...
随机推荐
- 值得一做》关于双标记线段树两三事BZOJ 1798 (NORMAL-)
这是一道双标记线段树的题,很让人很好的预习/学习/复习线段树,我不知道它能让别人学习什么,反正让我对线段树的了解更加深刻. 题目没什么好讲的,程序也没什么好讲的,所以也没有什么题解,但是值得一做 给出 ...
- R list frame, matrix
list是个筐,什么都可以往里装,包括它自身.数据框是个二维数组,同列必须同类型,行随意.
- SQL将表中某一类型的一列拼接成一行
SELECT TypeName ,(SELECT ','+ UserName FROM [ContainerMembers] t WHERE TypeName= aa.TypeName FOR XML ...
- markdown的图片外链
markdown的图片用本地的很不方便,今天试用了一下七牛的服务,感觉很好用.推荐一下,免费的服务够用并且比较友好.
- Oracle数据库之单表查询
接着上一篇的分享,今天主要给大家分享的是关于数据中的单表查询,单表查询很基础,也很重要,但是任何一个初学者必须要掌握的姿势,单表查询就是对单个表进行操作,查询我们想要的数据.单表查询里面的内容也是比较 ...
- (转)那天有个小孩教我WCF[一][1/3]
原文地址:http://www.cnblogs.com/AaronYang/p/2950931.html 既然是小孩系列,当然要有一点基础才能快速掌握,归纳,总结的一个系列,哈哈 前言: 第一篇嘛,不 ...
- 编写高质量代码改善C#程序的157个建议——建议15: 使用dynamic来简化反射实现
建议15: 使用dynamic来简化反射实现 dynamic是Framework 4.0的新特性.dynamic的出现让C#具有了弱语言类型的特性.编译器在编译的时候不再对类型进行检查,编译器默认dy ...
- 通过fork函数创建进程的跟踪,分析linux内核进程的创建
作者:吴乐 山东师范大学 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.实验过程 1.打开gdb, ...
- 探索Asp.net mvc 的文件上传(由浅入深)
代码下载:http://files.cnblogs.com/n-pei/mvcfileupload.zip 最近因为TeamVideo需要用到视频和图片上传功能,所以试着Google了很多资料,和大家 ...
- 【转】MOCK方法介绍
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://baidutech.blog.51cto.com/4114344/743740 1 ...