c++ boost::has_nothrow_constructor实际上对boost::variant做了什么?

z0qdvdin  于 2023-01-18  发布在  其他
关注(0)|答案(1)|浏览(136)

boost::variant的上下文中,我理解boost::has_nothrow_copy保持复制,不分配被覆盖到堆上的对象,以防复制抛出,原始对象需要恢复(尽管我有点惊讶它没有移动)。
但是,我不清楚boost::has_nothrow_constructor的用途。为什么它需要这个?在文档中它说:

启用优化

...

  • 如果 any 绑定类型不可抛出默认构造(如boost::has_nothrow_constructor所示),库保证variant将仅对variant中的 * 每个 * 有界类型使用单个存储和就地构造。但是,请注意,如果赋值失败,未指定的nothrow可默认构造的有界类型将在左手侧操作数中被默认构造,以便保持从不为空的保证。

这似乎表明,如果没有对std::true_type的专门化,这将导致使用多存储或非就地构造。
注意:我实际上发现this pdf更容易阅读,因为它有更多的示例。

ctzwtxfj

ctzwtxfj1#

答案就在您引用的Boost.Variant文档中。
Boost.Variant维护了一个永不为空的保证,这意味着无论boost::variant的生存期内发生了什么,它都保证只包含一个对象,该对象的类型是其模板参数中列出的类型之一。这给boost::variant的赋值带来了一个问题--如果赋值失败并出现异常,会发生什么?
例如,让我们考虑下面的代码:

std::string str("Some very long string");
boost::variant< int, std::string > var(10);
var = str;

最初,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的默认构造函数实际上没有初始化它)。

相关问题