std::unique_ptr 和自定义删除程序

std::unique_ptr and custom deleters

本文关键字:自定义 删除程序 unique std ptr      更新时间:2023-10-16

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 情况存储类似的内容?

正如@milleniumbug所说,std::unique_ptr使用Empty Base Optimization。这意味着您可以声明一个没有数据成员的类:

class empty
{
public:
// methods
};

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

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

在这种情况下,foo的大小将为 8 字节。但是如果你声明fooempty继承,这个继承对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;

实际上,如果第二个模板参数是一个空类,则此类是专用的。在这种情况下,它继承自空的 deleter 类,并声明第一个模板参数的成员变量,该参数std::unique_ptr指针。