在boost::variant
的上下文中,我理解boost::has_nothrow_copy
保持复制,不分配被覆盖到堆上的对象,以防复制抛出,原始对象需要恢复(尽管我有点惊讶它没有移动)。
但是,我不清楚boost::has_nothrow_constructor
的用途。为什么它需要这个?在文档中它说:
启用优化
...
- 如果 any 绑定类型不可抛出默认构造(如
boost::has_nothrow_constructor
所示),库保证variant
将仅对variant
中的 * 每个 * 有界类型使用单个存储和就地构造。但是,请注意,如果赋值失败,未指定的nothrow可默认构造的有界类型将在左手侧操作数中被默认构造,以便保持从不为空的保证。
这似乎表明,如果没有对std::true_type
的专门化,这将导致使用多存储或非就地构造。
注意:我实际上发现this pdf更容易阅读,因为它有更多的示例。
1条答案
按热度按时间ctzwtxfj1#
答案就在您引用的Boost.Variant文档中。
Boost.Variant维护了一个永不为空的保证,这意味着无论
boost::variant
的生存期内发生了什么,它都保证只包含一个对象,该对象的类型是其模板参数中列出的类型之一。这给boost::variant
的赋值带来了一个问题--如果赋值失败并出现异常,会发生什么?例如,让我们考虑下面的代码:
最初,
var
包含一个int
类型的值10,然后它被赋值一个std::string
类型的值,为了这个例子,让我们假设这个赋值失败并出现异常(例如std::bad_alloc
)。由于原始存储的值不是
std::string
类型,因此需要销毁该值。int
具有平凡析构函数,因此销毁它是一个空操作。但是,它所占用的存储空间会被新构造的std::string
重用,在本例中,它是从str
复制构造的。正如我们上面所建立的,此复制构造函数抛出:并且在var
中没有留下有效值-串构造失败并且先前的int
已经丢失。一种解决方案是使用堆分配的存储空间来构造字符串,然后再销毁变量中当前存储的值。如果构造成功,则销毁存储的值,变量在内部存储一个指向新构造的值的指针。这就是
boost::variant
所做的,只有少数例外。首先,如果被赋值的值的类型有一个非抛出的复制构造函数,那么整个问题就不存在了--复制构造函数可以保证不会失败,所以在复制之前销毁原始值是安全的。这是由
boost::has_nothrow_copy
检测到的。这不是我们在这个例子中的情况,因为std::string
的复制构造函数显然可以抛出异常。其次,如果
boost::variant
模板参数中列出的任何类型都有一个非抛出的默认构造函数,如boost::has_nothrow_constructor
所确定的,那么boost::variant
可以通过默认构造一个这样的类型的值来从失败的赋值中恢复过来。Boost.Variant没有指定如果多个类型都有一个非抛出的默认构造函数,那么将选择哪一个类型。除了一个特殊的类型boost::blank
将被优先考虑,如果列出的话。这个优化匹配我们的情况,因为int
的默认构造函数是平凡的,并且从不抛出。所以我们的例子中赋值的行为将如下所示:1.销毁当前存储的
int
类型的值。1.在
var
的内部存储中复制构造std::string
类型的新值。1.如果复制构造函数抛出异常,则在内部存储中默认构造一个
int
类型的值,并将异常传播给调用方。结果,
var
的值变为int
类型的不确定值(因为int
的默认构造函数实际上没有初始化它)。