间接成员 RAII:unique_ptr 还是可选的?
Indirect Member RAII: unique_ptr or optional?
考虑具有无法直接存储的成员的 class,例如,因为它没有默认构造函数,并且封闭的 class 的构造函数没有足够的创建它的信息:
class Foo
{
public:
Foo(){} // Default ctor
private:
/* Won't build: no default ctor or way to call it's
non-default ctor at Foo's ctor. */
Bar m_bar;
};
显然,m_bar
需要以不同方式存储,例如,通过指针。不过,std::unique_ptr
似乎更好,因为它会自动销毁它:
std::unique_ptr<Bar> m_bar;
也可以使用 std::experimental::optional
,不过:
std::experimenatl::optional<Bar> m_bar;
我的问题是:1. 权衡是什么?和 2. 构建一个 class 自动在它们之间进行选择是否有意义?
具体来说,查看 exception guarantees for the ctor of std::unique_ptr
and the exception guarantees for the ctor of std::experimental::optional
,似乎很清楚前者必须执行动态分配和释放 - 运行时速度劣势,而后者将内容存储在一些(对齐的)内存缓冲区中 - 大小劣势。这些是唯一的权衡吗?
如果这些确实是权衡,并且考虑到两种类型共享足够多的接口(ctor,operator*
),使用类似
的东西在它们之间自动进行选择是否有意义
template<typename T>
using indirect_raii = typename std::conditional<
// 20 - arbitrary constant
sizeof(std::experimental::optional<T>) >
20 + sizeof(std::exerimental::optional<T>)sizeof(std::unique_ptr<T>),
std::unique_ptr<T>,
std::experimental::optional<T>>::type;
(注:有一个question discussing the tradeoffs between these two as return类型,但是问答的重点是各自向函数的调用者传达了什么,与这些私有成员无关。)
IMO 这里还有其他权衡:
unique_ptr
不可复制或不可复制分配,而 optional
是。
我想你可以做的一件事是使 indirect_RAII
成为 class 类型并有条件地添加定义以通过调用 Bar
的复制构造函数使其可复制,即使 unique_ptr
是选择。 (或者相反,当它是可选的时禁用复制。)
optional
类型可以有一个 constexpr
构造函数——你不能在编译时用 unique_ptr
做同样的事情。
Bar
在构造 unique_ptr<Bar>
时可能不完整。在知道optional<Bar>
的时候不能不完整。在您的示例中,我猜您假设 Bar
是完整的,因为您采用了它的大小,但您可能希望在实际情况并非如此的情况下使用 indirect_RAII
实现 class。
- 即使在
Bar
很大的情况下,您仍然可能会发现,例如选择 optional
时 std::vector<Foo>
的性能会比选择 unique_ptr
时更好。我希望在 vector
被填充一次,然后迭代多次的情况下发生这种情况。
作为一般经验法则,您的大小规则可能适合在您的程序中普遍使用,但我想对于 "common use",您选择哪一个并不重要。使用 indirect_RAII
类型的另一种方法是,在每种情况下选择一个或另一个,并且在您可以利用 "generic interface" 的地方,在必要时将类型作为模板参数传递。在性能关键区域,手动做出适当的选择。
考虑具有无法直接存储的成员的 class,例如,因为它没有默认构造函数,并且封闭的 class 的构造函数没有足够的创建它的信息:
class Foo
{
public:
Foo(){} // Default ctor
private:
/* Won't build: no default ctor or way to call it's
non-default ctor at Foo's ctor. */
Bar m_bar;
};
显然,m_bar
需要以不同方式存储,例如,通过指针。不过,std::unique_ptr
似乎更好,因为它会自动销毁它:
std::unique_ptr<Bar> m_bar;
也可以使用 std::experimental::optional
,不过:
std::experimenatl::optional<Bar> m_bar;
我的问题是:1. 权衡是什么?和 2. 构建一个 class 自动在它们之间进行选择是否有意义?
具体来说,查看 exception guarantees for the ctor of std::unique_ptr
and the exception guarantees for the ctor of std::experimental::optional
,似乎很清楚前者必须执行动态分配和释放 - 运行时速度劣势,而后者将内容存储在一些(对齐的)内存缓冲区中 - 大小劣势。这些是唯一的权衡吗?
如果这些确实是权衡,并且考虑到两种类型共享足够多的接口(ctor,operator*
),使用类似
template<typename T>
using indirect_raii = typename std::conditional<
// 20 - arbitrary constant
sizeof(std::experimental::optional<T>) >
20 + sizeof(std::exerimental::optional<T>)sizeof(std::unique_ptr<T>),
std::unique_ptr<T>,
std::experimental::optional<T>>::type;
(注:有一个question discussing the tradeoffs between these two as return类型,但是问答的重点是各自向函数的调用者传达了什么,与这些私有成员无关。)
IMO 这里还有其他权衡:
unique_ptr
不可复制或不可复制分配,而optional
是。
我想你可以做的一件事是使indirect_RAII
成为 class 类型并有条件地添加定义以通过调用Bar
的复制构造函数使其可复制,即使unique_ptr
是选择。 (或者相反,当它是可选的时禁用复制。)optional
类型可以有一个constexpr
构造函数——你不能在编译时用unique_ptr
做同样的事情。Bar
在构造unique_ptr<Bar>
时可能不完整。在知道optional<Bar>
的时候不能不完整。在您的示例中,我猜您假设Bar
是完整的,因为您采用了它的大小,但您可能希望在实际情况并非如此的情况下使用indirect_RAII
实现 class。- 即使在
Bar
很大的情况下,您仍然可能会发现,例如选择optional
时std::vector<Foo>
的性能会比选择unique_ptr
时更好。我希望在vector
被填充一次,然后迭代多次的情况下发生这种情况。
作为一般经验法则,您的大小规则可能适合在您的程序中普遍使用,但我想对于 "common use",您选择哪一个并不重要。使用 indirect_RAII
类型的另一种方法是,在每种情况下选择一个或另一个,并且在您可以利用 "generic interface" 的地方,在必要时将类型作为模板参数传递。在性能关键区域,手动做出适当的选择。