std::unique_ptr和自定义删除器

3
Scott Meyer的“Effective Modern C++”讨论了使用具有自定义删除器的std::unique_ptr,并指出:

函数指针作为删除器通常会导致std::unique_ptr的大小从一个字增长到两个字。对于函数对象作为删除器,则根据函数对象存储了多少状态而定。无状态函数对象(例如没有捕获的lambda表达式)不会产生大小惩罚,这意味着当自定义删除器可以实现为函数或无捕获lambda表达式时,应使用lambda表达式。

例如,以下内容:
auto delInvmt1 = [](Investment* pInvestment) {
makeLogEntry(pInvestment);
delete pInvestment;
};

template<typename... Ts>
std::unique_ptr<Investment, decltype(delInvmt1)>
makeInvestment(Ts&&... args);

这段代码:

优于

这段代码:

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

template<typename... Ts>
std::unique_ptr<Investment, void (*)(Investment*)>
makeInvestment(Ts&&... params);

我可以看到在第二种情况下,需要将删除器函数的指针存储在unique_ptr中,但为什么在lambda情况下不需要存储类似的内容呢?


2
std::unique_ptr 使用空基类优化,这允许存储空对象(即没有数据成员的类)而不会增加额外的大小开销。 - milleniumbug
4
第一种情况下,逻辑是类型的一部分,第二种情况下,它是的一部分。 - Kerrek SB
1个回答

3
正如@milleniumbug所说,std::unique_ptr使用空基类优化。这意味着您可以声明一个没有数据成员的类:
class empty
{
public:
   // methods
};

如果您有另一个类在其中声明了一个名为empty的成员变量,即使empty没有数据成员,您的类的大小也会增加:

class foo
{
public:
    int i;
    empty em;
};

在这种情况下,foo的大小将为8字节。但如果您声明foo继承自empty,则此继承对foo的大小没有影响,它的大小将为4字节。
class foo : public empty 
{
public:
    int i;
};

如果你查看你的编译器中std::unique_ptr的实现,你会看到这个。我使用的是VC++ 2015,在这个编译器中,std::unique_ptr的结构如下:

template<class _Ty, class _Dx>  // = default_delete<_Ty>
class unique_ptr : public _Unique_ptr_base<_Ty, _Dx>

它继承自这个类:

template<class _Ty, class _Dx>
class _Unique_ptr_base
{   // stores pointer and deleter
 public:
 ...
 _Compressed_pair<_Dx, pointer> _Mypair;
};

_Unique_ptr_base中有一个类型为_Compressed_pair的成员。该类声明如下:
template<class _Ty1, class _Ty2, bool = is_empty<_Ty1>::value && !is_final<_Ty1>::value>
class _Compressed_pair final
    : private _Ty1

{   // store a pair of values, deriving from empty first
private:
     _Ty2 _Myval2;

实际上,如果这个类的第二个模板参数是一个空类,那么它就是专门用来处理这种情况的。在这种情况下,它会继承自一个空的删除器类,并声明一个第一个模板参数的成员变量,这个成员变量是指向std::unique_ptr的指针。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接