Item 18: 使用srd::unique_ptr来管理独占所有权的资源

本文翻译自《effective modern 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

            你要记住的事
  • std::unique_ptr是一个小的,快的,mov-only的智能指针,它能用来管理资源,并且独占资源的所有权。
  • 默认情况下,资源的销毁是用过delete进行的,但是自定义deleter能指定销毁的行为。用带状态的deleter和函数指针作为deleter会增加std::unique_ptr对象的大小。
  • 从std::unique_ptr转换到std::shared_ptr很简单。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值