智能指针是行为类似于指针的类对象,单这种对象还有其他功能。本节介绍三个可帮助管理动态内存分配的智能指针类。先来看看需要哪些功能以及这些功能是如何实现的。请看下面的函数:
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类似。

转载于:https://www.cnblogs.com/moonlightpoet/p/5677525.html

《C++ Primer Plus》16.2 智能指针模板类相关推荐

  1. C++智能指针模板类

    对于常规类指针,可能由于忘记释放内存而导致内存泄漏,有三种智能指针可以解决这类问题. 对于常规指针,它没有析构函数,加入指针成为了对象,那么,在对象过期时就会自动调用析构函数,让析构函数释放指针指向的 ...

  2. 一个自己实现的简单的智能指针模板类

    一个智能指针的小程序,使用模板函数开发,附带测试程序 代码如下: ///auto ptr template<typename T> class AutoPtr { struct t_ptr ...

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

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

  4. 【C++深度剖析教程16】智能指针的分析

    今天我们来学习C++中的一个独有的特性,智能指针.智能指针的作用非常的强大,它解决了C++语言关于指针部分内存泄漏的BUG.那么在此处,内存泄漏指的是什么呢? -动态申请堆空间,用完后不归还 -C++ ...

  5. VTK源码阅读--VTK里的智能指针vtkSmartPointer类

    vtkSmartPointer类 vtkSmartPointer是一个类模板,为vtkSmartPointerBase超类持有的对象提供自动强制转换. #ifndef vtkSmartPointer_ ...

  6. 三维重建16:概率图模型 模板类编程

    刚刷了一部分网络题,又出了个模板类编程.没人能从面试中得到自己想要的方法,只能得到能看得到的结果!!! 一 概率图模型 贝叶斯模型,真是推导不出来了!贝叶斯函数貌似也写不出来了! 参考:斯坦福概率图模 ...

  7. 资源共享型智能指针实现方式

    [1]资源共享型智能指针实现方式简述 资源共享型的智能指针有两种实现方式:一种是侵入式:一种是非侵入式. 网上以及书籍比较常见的是非侵入式的,它的实现完全放在智能指针模板类内. 模板类有一个用于保存资 ...

  8. c++string 加引号_C++|引用计数与shared_ptr智能指针(以实现String类为例)

    C++ 中,动态内存的管理是通过一对运算符来完成的,new 用于申请内存空间,调用对象构造函数初始化对象并返回指向该对象的指针.delete接收一个动态对象的指针,调用对象的析构函数销毁对象,释放与之 ...

  9. c++中的智能指针详解

    智能指针是行为类似于指针的类对象,但是这种对象还有其他的功能.一般用于帮助管理动态内存. 我们需要知道,我们每次在对象中new出一块内存空间都需要在对象销毁时delete掉,否则就会造成内存泄露,这些 ...

最新文章

  1. 【数据结构】顺序表的应用(4)(C语言)
  2. cocos2d CCNode类(节点属性大全)
  3. 科大星云诗社动态20210413
  4. cctype,string,vector
  5. 【Java数据库】ORM思想:对象关系映射 使用Java容器存储多条记录
  6. 零基础跟我学前端之css3基础
  7. Holedox Moving
  8. CentOS上完全卸载gitlab
  9. 1039. 到底买不买(20)-PAT乙级真题
  10. 重学JavaScript系列之一_引用类型
  11. TimeLine下载地址
  12. 怎么设置竖线分栏_做出专业级的Word文档分栏,只需这3个技巧
  13. 利用.jou文件将建筑shp文件导入Gambit方法
  14. 软件安全性验收测试 软件测试常见报告类型
  15. 基于感知器准则的线性分类器设计
  16. 【收藏】QCIF、 CIF、2CIF、DCIF、D1(4CIF)格式介绍
  17. html把保留图片改为提交按钮,如何制作图片按钮,并为图片按钮添加提交表单和重置表单功能...
  18. 计算机乱七八糟小知识备忘录
  19. 职业生涯步步高(转)
  20. Android 源码分析 - 输入 - 细节

热门文章

  1. IT人 不要一辈子靠技术生存(转)
  2. 使用haproxy做负载均衡时保持客户端真实的IP
  3. 执行计划中各字段各模块描述
  4. mysql 批量替换域名_msyql 中批量替换url网址中的域名方案。
  5. Mysql orangepi_SSH远程登录香橙派Orange Pi Zero2开发板的操作方法
  6. python如何在文本内排序_在python中对文本文件中的项进行排序
  7. Go语言基础(一)——HelloWorld
  8. hdp对应hadoop的版本_好程序员大数据学习路线分享hadoop的知识总结
  9. 沈阳java基础培训,辽宁沈阳java培训学费大概多少
  10. SolidWorks转3DMAX