Effective Modern C++ Item 18

Item 18: Use std::unique_ptr for exclusive-ownership resource management

std::unique_ptr 提供对象的独占所有权。不能复制 std::unique_ptr,只允许移动,,它将所有权从源指针转移到目标指针。

std::unique_ptr的常见用法是作为工厂函数返回类型。

1
2
3
4
class Investment { … };
class Stock: public Investment { … };
class Bond: public Investment { … };
class RealEstate: public Investment { … };

Investment继承关系的工厂函数可以这样声明:

1
2
3
template<typename... Ts>            //返回指向对象的std::unique_ptr,
std::unique_ptr<Investment> //对象使用给定实参创建
makeInvestment(Ts&&... params);

默认情况下,当 std::unique_ptr 超出生命周期范围时,销毁将通过delete进行,但是在构造过程中,std::unique_ptr对象可以被设置为使用自定义删除器

假设想要在 Investment 对象被销毁之前打印日志,可以按如下方式实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
auto delInvmt = [](Investment* pInvestment)         //自定义删除器
{ //(lambda表达式)
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 (/*一个Stock对象应被创建*/)
{
pInv.reset(new Stock(std::forward<Ts>(params)...));
}
else if ( /*一个Bond对象应被创建*/ )
{
...
}
...
return pInv;
}

从这个例子可以看出:

  • 所有的自定义的删除行为接受要销毁对象的原始指针,然后执行销毁操作。
  • 使用自定义删除器时,删除器类型必须作为第二个类型实参传给std::unique_ptr
  • 为了将自定义删除器与智能指针关联,需要把自定义删除器作为构造函数的第二个实参。
  • 不能将原始指针(比如new创建)赋值给std::unique_ptr,从原始指针到智能指针的隐式转换会出问题。
  • 自定义删除器的一个形参,类型是基类类型,不管创建的对象的真实类型是基类还是子类,为此基类必须有虚析构函数。

使用默认删除器时(如delete),std::unique_ptr对象和原始指针大小相同。当使用自定义删除器时,可能会增加std::unique_ptr 的大小。函数指针形式的删除器,会使std::unique_ptr的大小增加一个字。对于函数对象形式的删除器来说,变化的大小取决于函数对象中存储的状态多少,无状态函数对象(比如不捕获变量的lambda表达式)对大小没有影响,因此尽量使用lambda

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
auto delInvmt1 = [](Investment* pInvestment)        //无状态lambda的
{ //自定义删除器
makeLogEntry(pInvestment);
delete pInvestment;
};

template<typename... Ts> //返回类型大小是
std::unique_ptr<Investment, decltype(delInvmt1)> //Investment*的大小
makeInvestment(Ts&&... args);

void delInvmt2(Investment* pInvestment) //函数形式的
{ //自定义删除器
makeLogEntry(pInvestment);
delete pInvestment;
}
template<typename... Ts> //返回类型大小是
std::unique_ptr<Investment, void (*)(Investment*)> //Investment*的指针
makeInvestment(Ts&&... params); //加至少一个函数指针的大小

std::unique_ptr有两种形式,一种用于单个对象(std::unique_ptr<T>),一种用于数组(std::unique_ptr<T[]>)。

std::unique_ptr还可以轻松高效的转换为std::shared_ptr

1
2
std::shared_ptr<Investment> sp =            //将std::unique_ptr
makeInvestment(arguments); //转为std::shared_ptr

重点

  • std::unique_ptr 是一个轻量、快速、只能移动(move-only)的智能指针,用于管理具有独占所有权的资源。
  • 默认情况下, 资源销毁通过delete实现,但可以指定自定义删除器。
  • 有状态的自定义删除器和函数指针作为删除器会增加 std::unique_ptr 对象的大小。
  • std::unique_ptr 转换为 std::shared_ptr 很容易方便。