智能指针是行为类似于指针的类对象,单这种对象还有其他功能。本节介绍三个可帮助管理动态内存分配的智能指针类。先来看看需要哪些功能以及这些功能是如何实现的。请看下面的函数:
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将不被执行,因此也将导致内存泄漏。
可以按第14章介绍的方式修复这种问题,单如果有更灵巧的解决方法就好了。来看以下需要些什么。当remodel()这样的函数终止(不管是正常终止,还是由于出现了异常而终止),本地变量都将从栈内存中删除——因此指针ps占据的内存将被释放。如果ps指向的内存也被释放,那该多好阿。如果ps有一个析构函数,该析构函数将在ps过期时释放它指向的内存。因此,ps的问题在于,它指示一个常规指针,不是有析构函数的类对象。如果它是对象,则可以在对象过期时,让它的析构函数删除指向的内存。这正式auto_ptr、unique_ptr和shared_ptr背后的思想。模板auto_ptr是C++98提供的解决方案,C++11已将其摒弃,并提供了另外两种解决方法。然而,虽然auto_ptr被摒弃,单它已经使用了多年;同时,如果您的编译器不支持其他两中解决方法,
auto_ptr将是唯一的选择方案。

16.2.1 使用只能指针
这三个只能指针模板(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();
...};
throw()意味着构造函数不会引发异常;与auto_ptr一样,throw()也被摒弃。因此,请求X类型的auto_ptr将获得一个指向X类型的auto_ptr:
auto_ptr<double> pd(new double);    // pd an auto_ptr to double
                                    // (use in place of double * pd)
auto_ptr<string> ps(new string);    // ps an auto_ptr to string
                                    // (use in place of string * ps)
new double是new返回的指针,指向新分配的内存块。它是构造函数auto_ptr<double>的参数,即对应于原型中形参p的实参。同样,new string也是构造函数的实参。其他两种只能指针使用同样的语法:
unique_ptr<double> pdu(new double); // pdu an unique_ptr to double
shared_ptr<string> pss(new string); // pss a shared_ptr to string
因此,要转换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中。程序清单16.5是一个简单的程序,演示了如何使用全部三种只能指针。要编译该程序,您的编译器必须支持C++11新增的类share_ptr和unique_ptr。每个只能指针都放在一个代码块内,这样离开代码块时,指针将过期。Report类使用方法报告对象的创建和销毁。
程序清单16.5 smrtptrs.cpp
--------------------------------------------------
// 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 0;
}
--------------------------------------------------
(注:我的G++版本没有shared_ptr和unique_ptr)

16.2.3 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("quto"));  //#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 trmp;
}
并假设编写了如下代码:
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将存在一段时间,编译器将禁止这样做:
using namespace std;
unique_ptr<string> pu1(new string "Ho 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(pu1),这可能导致危害。语句#2不会留下悬挂的unique_ptr,因为它调用unique_ptr的构造函数,代构造函数创建的临时对象能够在其所有权转让给pu后就会被销毁。这种随情况而异的行为表示,unique_ptr优于允许两种赋值的auto_ptr。这也是禁止(只是一种建议,编译器并不禁止)在容器对象中使用auto_ptr,单允许使用unique_ptr的原因。如果容器算法试图对包含unique_ptr的容器执行类似于语句#1的操作,将导致编译错误;如果算法试图执行类似于语句#2的操作,则不会有任何问题。而对于auto_ptr,类似于语句#1的操作可能导致不确定的行为和神经的崩溃。
当然,您肯呢哥确实像执行类似于语句#1的操作。仅当以废止能的方式使用一起的只能指针(如解除引用时),这种复制才不安全。要安全地重用这种值帧,可给它赋新值。C++有一个标准库函数std::move(),让您能够将一个unique_ptr赋给另一个。下面是一个使用前述demo()函数的例子,该函数返回一个unique_ptr<string>对象:
using namespace std;
unique_ptr<string> ps1, ps2;
ps1 = demo("Uniquely special");
ps2 = move(ps1);        // enable assignment
ps1 = demo(" and more");
cout << *ps2 << *ps2 << endl;
unique_ptr如何能够区分安全和不安全的用法呢?答案是它使用了C++11新增的移动构造函数和右值引用,浙江在第18章讨论。
相比于auto_ptr,unique_ptr还有另一个有点。它有一个可用于数组的变体。别忘了,必须将delete和new配对,将delete []和new []配对。模板auto_ptr使用delete而不是delete [],因此只能与new一起使用。而不能与new []一起使用。单unique_ptr有使用new []和delete []的版本:
std::unique_ptr<double []> pda(new double(5));    // will use delete []
注:使用new分配内存时,才能使用auto_ptr和shared_ptr,使用new []分配内存时,不能使用它们。不使用new分配内存时,不能使用auto_ptr或shared_ptr;不使用new或new []分配内存时,不能使用unique_ptr。

16.2.4 选择智能指针
应使用哪种只能指针呢?如果程序要使用多个指向同一个对象的指针,应选择shared_ptr。这样的情况包括:有一个指针数组,并使用一些辅助指针来标识特定的元素,如最大的元素和最小的元素;来那个哥随想包含都指向第三个对象的指针;STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出警告)和auto_ptr(行为不确定)。如果您的编译器没有提供shared_ptr,可使用Boost库提供的shared_ptr。
如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr。如果函数使用new分配内存,并返回指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权将转让为接受返回值的unique_ptr,而该只能指针将负责调用delete。可将unique_ptr存储到STL容器中,只要不调用将一个unique_ptr赋值或赋给另一个的方法或算法(如sort())。例如,可在程序中使用类似于下面的代码段,这里假设程序包含正确的include和using语句:
unique_ptr<int> make_int(int n)
{
    return unique_ptr<int>(new int(n));
}
void show(unique_ptr<int> & pi)     // pass by reference
{
    cout << *a << ' ';
}
int main()
{
    vector<unique_ptr<int> > vp(size);
    for (int i = 0; i < vp.size(); i++)
        vp[i] = make_int(rand() % 1000);    // copy temporary unique_ptr
    vp.push_back(male_int(rand() % 1000));  // ok because arg is temporary
    for_each(vp.begin(), vp.end(), show);   // use for_each()
...
}
其中的push_back()调用没有问题,因为它返回一个临时unique_ptr,该unique_ptr被赋给vp中的一个unique_ptr。另外,如果按值而不是按引用给show()传递对象,for_each()语法将非法,因为这将导致使用一个来自vp的非临时unique_ptr初始化pi,而这时不允许的。前面说国,编译器将发现错误使用unique_ptr的企图。
在unique_ptr为右值时,可将其赋给shared_ptr,这与将一个unique_ptr赋给另一个需要满足的条件相同。与前面一样,在下面的代码中,make_int()的返回类型为unique_ptr<int>:
unique_ptr<int> pup(make_int(rand() % 1000));   // ok
shared_ptr<int> spp(pup);                       // not allowed, pup an lvalue
shared_ptr<int> spr(make_int(rand() % 1000));   // ok
模板shared_ptr包含一个显式构造函数,可用于将右值unique_ptr转换为shared_ptr。shared_ptr将接管原来归unique_ptr所有的对象。
在满足unique_ptr要求的条件时,也可使用auto_ptr,但unique_ptr是更好的选择。如果您的编译器没有提供unique_ptr,可考虑使用BOOST库提供的scoped_ptr,它与unique_ptr类似。

《C++ Primer Plus》16.2 智能指针模板类的更多相关文章

  1. C++_类入门5-智能指针模板类

    智能指针是行为类似于指针的类对象,但这种对象还有其他功能. 本节介绍三个可帮助管理动态内存分配的智能指针模板(auto_ptr.unique_ptr和shared_ptr). void remodel ...

  2. C++智能指针管理类

    1.程序员明确的进行内存释放 对于c++程序员,最头脑的莫过于对动态分配的内存进行管理了.c++在堆上分配的内存,需要程序员负责对分配的内存进行释放.但有时内存的释放看起来并不件很轻松的事,如下程序 ...

  3. [cocos2dx注意事项014]一个用于cocos2dx对象智能指针模板

    现在,C++有许多实现智能指针,一个更无所谓.哈. 这种智能指针是专为cocos2dx 2.2.x自定义.主要的易用性,同时必须遵循现有的cocos2dx内存管理.特殊实现这样的智能指针.无需在使用时 ...

  4. 【C++ Primer 第16章】2. 模板实参推断

    模板实参推断:对于函数模板,编译器利用调用中的函数实参来确定模板参数,从函数实参来确定模板参数的过程被称为模板实参推断. 类型转换与模板类型参数 与往常一样,顶层const无论在形参中还是在是实参中, ...

  5. C++笔记(11) 智能指针

    1. 设计思想 智能指针是行为类似于指针的类对象,但这种对象还有其他功能.首先,看下面的函数: void remodel(std::string & str) { std::string * ...

  6. 根据OSG中的ref_ptr和Reference简化的智能指针

    main.cpp测试代码 #include "TestSmartPointer" void fun() { SP<TestSmartPointer> sp1=new T ...

  7. STL模板_智能指针概念

    一.智能指针1.类类型对象,在其内部封装了一个普通指针.当智能指针对象因离开作用域而被析构时,其析构函数被执行,通过其内部封装的普通指针,销毁该指针的目标对象,避免内存泄露.2.为了表现出和普通指针一 ...

  8. ZT自老罗的博客 Android系统的智能指针(轻量级指针、强指针和弱指针)的实现原理分析

    Android系统的智能指针(轻量级指针.强指针和弱指针)的实现原理分析 分类: Android 2011-09-23 00:59 31568人阅读 评论(42) 收藏 举报 androidclass ...

  9. c++基础 使用智能指针

    三个智能指针模板(auto_ptr.unique_ptr和shard_ptr)都定义了类似指针的对象(c++11已将auto_ptr摒弃),可以将new获得(直接或间接) 的地址赋给这种对象.当智能指 ...

随机推荐

  1. unity------------------------------transform.forward与Vector.forward的区别

    在unity3d中有2个forward,一个是vector3.forward和transform.forward,这两个forward其实完全不一样.他们之间的区别主要体现在在不同坐标系时的反映上. ...

  2. erlang的erl文件的编码方式

    在数据源头的文件第一行加上%%coding: latin-1

  3. SpringMVC文件上传基础

    maven依赖 <!--文件上传所需--> <dependency> <groupId>commons-fileupload</groupId> < ...

  4. MYSQL之You can't specify target table for update in FROM clause解决办法

    mysql中You can't specify target table for update in FROM clause错误的意思是说,不能先select出同一表中的某些值,再update这个表( ...

  5. 在阿里云创建子域名,配置nginx,使用pm2部署node项目到ubuntu服务器

    配置域名 在阿里云找到主域名 进入主域名之后,右上角添加解析,添加子域名, 记录类型选择cname,主机记录填写子域名的名称,记录值为主域名,至此阿里云已经配置好了. 检查nginx安装 首先检查服务 ...

  6. 分页功能实现之通过ajax实现表单内容刷新

    拿代码来说话 我们的需求就是点击翻页功能,实现表格内容局部刷新且能够翻到对应的页面上,不明白? 那么就看看下面的图,需要达到的效果如下所示: 现在要实现的功能就是把红线框起来的表单内容 在点击翻页的时 ...

  7. python-反射、新式类与经典类搜索的优先级

    preface include: getattr setattr delattr hasattr class webserver(object): def __init__(self,num): se ...

  8. 6 云计算系列之Nova安装与配置

    preface 上面安装好了glance,下面就开始部署nova计算服务了. nova组件介绍 首先介绍下nova各个组件. api 用来接收和响应外部的请求唯一途径,支持Openstack api, ...

  9. 【QT】Cannot find file: untitled.pro,项目路径不要包含中文。

    Cannot find file: D:\文件及下载相关\文档\untitled\untitled.pro. 17:01:45: 进程"D:\Englishpath\QT5.9.3\5.9. ...

  10. [SLAM] 02. Some basic algorithms of 3D reconstruction

    链接:http://www.zhihu.com/question/29885222/answer/100043031 三维重建 3D reconstruction的一个算法思路介绍,帮助理解 首先一切 ...