Item 18: 使用srd::unique_ptr来保管专所有权的资源

本文翻译自modern effective
C++,由于水平有限,故无法确保翻译完全正确,欢迎提议错误。谢谢!

博客已经搬至这里啦

当您待一个智能指针的时候,std::unique_ptr常常是绝相仿你需要的这些。默认情形下,这么假如是死有理的:std::unique_ptr和原始指针的轻重缓急是均等的,并且多操作(包括消除引用),它们执行的凡完全相同的命令。那表示你甚至会将其用在对内存和时间还特别拮据的地点。假使一个原始指针对君来说够的有些和抢,那么一个std::unique_ptr也几可毫无疑问是这般的。

std::unique_ptr表现来把所有权的语义。一个非空的std::unique_ptr总是针对它对的资源具有所有权。move一个std::unique_ptr将将所有权从源指针转交给目的指针(源指针将为安装为null)。拷贝一个std::unique_ptr是休为允许的,因为只要你拷贝一个std::unique_ptr,你用拿到两独std::unique_ptr指向同样的资源,然后立时半只指针都觉着她拥有资源(由此应该释放资源)。由此std::unique_ptr是一个move-only(只好进展move操作的)类型。再看资源的灭绝,一个非空的std::unique_ptr销毁它的资源。默认情形下,通过以std::unique_ptr中delete一个原始指针的法子来进展资源的灭绝。

std::unique_ptr的常用方法是当做一个厂函数的返路(指向类层次中之对象),假要我们来一个投资类的好像层次(比如,股票,债券,不动产等等),那么些近乎层次的基类是Investment。

class Investment{ ... };

class Stock:
    public Investment { ... };

class Bond:
    public Investment { ... };

class RealRstate:
    public Investmemt { ... };

对此这么的类层次,一个厂子函数平时会于积上分红一个对象,并且再次来到一个对这目的的指针,当那目的不再用为利用的时刻,调用者有权利销毁之目的。这完全符合std::unique_ptr的定义,因为调用者要针对厂回到的资源负责(也就是是,它把了所有权),然后当std::unique_ptr被灭绝的时候,std::unique_ptr会自动销毁它对的目的。对于Investment类层次,一个厂函数能叫声称成这样:

template<typename... Ts>            //通过给定的参数,创建一个对象
std::unique_ptr<Investment>         //然后,返回一个这个对象
makeInvestment(Ts&&... params);     //的std::unique_ptr

调用者能当一个效率域中像下这样使所返的std::unique_ptr:

{
    ...         

    auto pInvestment =                  //pInvestment的类型是
        makeInvestment( arguments );    //std::unique_ptr<Investment>

    ...
}                                       //销毁*pInvestment

但是他们吧会管其之所以在“转移所有权”的语义中,比如说当工厂回到的std::unique_ptr被move到容器被失去了,容器中之要素就让move到一个目的的积极分子变量中失去了,然后那一个目的后会叫销毁。当这目标为灭绝时,对象的std::unique_ptr成员变量也拿为销毁,然后她的灭绝会招由工厂回到的资源被灭绝。倘若由一个很要另外的非正常的控制流(比如,在循环中return或break
),所有权链被由断了,持有被管理资源的std::unique_ptr最后依然会晤调用它的析构函数,因而给管理的资源如故碰头叫销毁。

默认境况下,销毁是经过delete举办的,然则,在销毁的时刻,std::unique_ptr对象会调动用从定义的deleter(销毁函数):当资源用给销毁之时段,任意的自定义函数(或仿函数,包括经lambda表明式发生的仿函数)将受调用。倘诺由于makeInvestment创建的靶子不该平昔delete,而是要事先勾勒下日志记录,makeInvestment能让实现成为下边这样(代码前边随着注释,所以只要您看到部分勿显然的代码,不需操心)

//自定义deleter(一个lambda表达式)
auto delInvmt = [](Investment* pInvestment)
                {
                    makeLogEntry(pInvestment);
                    delete pInvestment;
                };

template<typename... Ts>
std::unique_ptr<Investment, decltype(delInvmt)>
makeInvestment(Ts&&... params)
{
    std::unique_ptr<investment, decltype(delInvmt)>
        pInv(nullptr, delInvmt);

    if( /* 一个股票对象需要被创建*/)
    {
        pInv.reset(new Stock(std::forward<Ts>(params)...));
    }
    else if( /* 一个债券对象需要被创建*/)
    {
        pInv.reset(new Bond(std::forward<Ts>(params)...));
    }
    else if( /* 一个不动产对象需要被创建*/)
    {
        pInv.reset(new RealEstate(std::forward<Ts>(params)...));
    }

    return pInv;
}

自身立会说即刻是怎么工作之,然最近,大家事先考虑下一旦你是一个调用者,你而召开的政工看起会怎样。假使把makeInvestment重返的结果存放在auto变量中,你是活在幸福中的,因为您切莫需明白您利用的资源在销毁时用特别对待。事实上,你确实是沐浴在幸福中,因为std::unique_ptr的使意味着,当资源灭绝之上你莫需关怀它是怎销毁的,更非欲确保程序的各级一样漫漫实施路径中,资源还委实可以举行销毁。std::unique_ptr自动地拿这多少个工作还举办了。从一个客户的角度来说,makeInvestment的接口是非凡的。

使而理解了下的东西,你会晤发觉她的兑现呢是死好的:

  • delInvmt是于makeInvestment重回的对象(std::unique_ptr对象)的自定义deleter,所有的自定义销毁函数接受一个原始指针(那些指针指于内需给销毁之资源),然后开一些在销毁对象时要召开的行,我们的这种境况,函数的作为就是是调用makeLogEntry并且调用delete。使用一个lambda表明式来创制delInvmt是颇有利的,不过咱快速就能顾,比打一个习俗习惯的函数来说,它再也连忙。

  • 当一个自定义deleter被使用的时候,它的品类需要作为std::unique_ptr模板的亚独参数。大家的这种气象,就是delInvmt的花色,并且那吗就是是干什么makeInvestment的归来路是std::unique_ptr。(关于decltype的信息,请看Item
    3。)

  • makeInvestment最中央的方针是使创一个null
    std::unique_ptr,然后被其对一个型符合要求的目的,然后回到她。为了拿于定义deleter
    delInvmt和pInv关联起来,我们要把她看成构造函数的亚独参数传入。

  • 尝把一个原始指针(比如,从new重回的)赋值给一个std::unique_ptr是心有余而力不足透过编译的,因为就将形成从原始指针到智能指针的隐式转换,这样的隐式转换是起题目标,所以C++11底智能指针禁止这样的变换。这吗就是是干吗reset被用来:让pInv拿到对象(通过new创制)的所有权。

  • 于每个new,我们采取std::forward来为污染被makeInvestment的参数能健全转发(看Item
    25)。这只要非常对象成立时,构造函数能得由调用者提供的有所音讯。

  • 从今定义deleter需要一个Investment*型的参数。不管makeInvestment中开创的对象的真正类型是啊(也不怕是,Stock,Bond或者RealEstate),它最终还是可以够当lambda表明式中,作为一个Investment*对象被delete掉。这代表我们拿由此一个基类指针delete一个选派生类对象。为了让这正常干活,基类(Investment)必须使生一个virutal析构函数:

    class Investment {
    public:
        ...
        virtual ~Investment();  
        ...         
    };
    

每当C++14受,由于函数重临值类型推导规则(看Item
3)的在,意味着makeInvestment能让实现成为更加从简与愈发封装的艺术:

/*
 译注:对于封装来说,由于前面的形式必须要先知道delInvmt的实例才能
 调用decltype(delInvmt)来确定它的类型,并且这个类型是只有编译器知
 道,我们是写不出来的(看Item 5)。然后返回值的类型中又必须填写
 lambdas的类型,所以只能把lambda放在函数外面。
 但是使用auto来进行推导就不需要这么做,即使把lambda表达式放里面,
 也是可以由编译器推导出来的。
*/

template<typename... Ts>
auto makeInvestment(Ts&&... params)         //使用auto推导返回值类型
{
    auto delInvmt = [](Investment* pInvestment)
                    {
                        makeLogEntry(pInvestment);
                        delete pInvestment;
                    };

    //下面都和以前一样  
    std::unique_ptr<investment, decltype(delInvmt)>
        pInv(nullptr, delInvmt);

    if( ... )
    {
        pInv.reset(new Stock(std::forward<Ts>(params)...));
    }
    else if( ... )
    {
        pInv.reset(new Bond(std::forward<Ts>(params)...));
    }
    else if( ... )
    {
        pInv.reset(new RealEstate(std::forward<Ts>(params)...));
    }

    return pInv;
}

本人当头里就说过,当用默认deleter(也虽然是,delete)时,你可以创制地设std::unique_ptr对象和原始指针的轻重是如出一辙。当由定义deleter参合进来时,情状或许就是非是这么了。当deleter是函数指针的下,平日会造成std::unique_ptr的轻重从1单字节增添及2单字节(32各之意况下)。对于仿函数deleter,变化的轻重缓急看重让仿函数着蕴藏的状态来些许。没有状态的仿函数(比如,不抓获变量的lambda表达式)碰到的尺寸的处是0(不碰面改变大小),这意味当由定义deleter能被实现为函数或lambda表明式时,lambda是重好之挑选:

auto delInvmt1 = [](Investment* pInvestment)
                {
                    makeLogEntry(pInvestment);
                    delete pInvestment;
                };

//这个函数返回的std::unique_ptr的大小和Investment*
//的大小一样
template<typename... Ts>
std::unique_ptr<Investment, decltype(delInvmt1)>
makeInvestment(Ts&&... args);


void delInvmt2(Investment* pInvestment)
{
    makeLogEntry(pInvestment);
    delete pInvestment;
};

//这个函数返回的std::unique_ptr的大小等于Investment*
//的大小加上一个函数指针的大小。
template<typename... Ts>
std::unique_ptr<Investment, void(*)(Investment*)>
makeInvestment(Ts&&... args);

拉动大量态的仿函数deleter会爆发大小很充足的std::unique_ptr。假如你发现一个自定义deleter让您的std::unique_ptr大至不可以承受,你或许用转移而的统筹了。

厂子函数不是std::unique_ptr唯一的应用情况。它们以促成Pimpl机制的下进一步盛行。这样的代码不是挺复杂,不过呢无是直了当的,所以我会以Item
22着提及,这个Item是致力为此话题的。

std::unique_ptr有点儿栽情势,一种植是为单个对象(std::unique_ptr)用底,另一样种植是给数组(std::unique_ptr)用底。由此,这里永远不会师有外模糊的状:对于std::unique_ptr指向的凡数组仍然单独的目标。std::unique_ptr的API的计划适合您的运习惯。举个例子,单个对象没下标操作(operator[]),同时数组的格局没有取消引用操作(operator*和operator->)。

std::unique_ptr数组的存应只好当你感兴趣之技能,因为相比从原始数组,std::array,std::vector以及std::string几乎总是还好的数据结构的精选。关于本人力所能及设想到之绝无仅有的气象令std::unique_ptr是起义之,这就唯有当你用类C的API时(并且它回到一个原始指针,指向堆上之勤组,同时你有着其的所有权)。

std::unique_ptr是于C++11负发布独占所有权的法门,可是其太吸引人之特色是,它可以简单并赶快地变到std::shared_ptr:

std::share_ptr<Investment> sp =         //从std::unique_ptr转换
    makeInvestment( arguments );        //到std::shared_ptr

立即即使是胡std::unique_ptr这么适合当作工厂函数的回值类型的关键所在。工厂函数不知晓调用者是否想只要拿对象用在独占所有权的语义上依旧共享所有权(也便是std::shared_ptr)的语义上。通过重返一个std::unique_ptr,工厂提供被调用者一个极端便捷之智能指针,不过他们无遮调用者把它换成为其再灵活的哥们儿(std::shared_ptr)。(关于std::shared_ptr的信息,继续看Item
19)

            你如切记的从事
  • std::unique_ptr是一个稍微的,快的,mov-only的智能指针,它亦可就此来管理资源,并且把资源的所有权。
  • 默认情状下,资源的灭绝是因而过delete举办的,但是由定义deleter能指定销毁之所作所为。用带状态的deleter和函数指针作为deleter会扩大std::unique_ptr对象的分寸。
  • 从std::unique_ptr转换到std::shared_ptr很简单。