std::variant和boost::variant有什么区别?

20

在这个SO问题的答案中:

C++标准库中boost::variant的等效物是什么?

提到了boost::variantstd::variant有所不同。

  • 对于使用这些类的人来说,它们有哪些不同之处?
  • 委员会表达了什么动机来采用带有这些差异的std::variant
  • 在编写任一代码时,我应该注意什么,以保持与切换到另一个代码的最大兼容性?

(动机是在C++17之前的代码中使用boost::variant)


@JohnZwinck:非常希望您能发表一下您的评论/答案,因为您写了我链接中的那个答案。 - einpoklum
5
我认为只有在与这个问题互动时,JohnZwinck才会收到通知。你可以在他回答另一个问题时发表评论来引起他的注意。 - DavidW
1
对于那些不知道的人,boost::variantboost::variant2是两个不同的库。问题是关于前者的,但值得注意的是,boost::variant2的官方文档指出它与std::variant是API兼容的,但有一些附加条件:https://www.boost.org/doc/libs/1_75_0/libs/variant2/doc/html/variant2.html#design_differences_with_stdvariant。另请参阅Boost项目列表:https://www.boost.org/doc/libs/1_75_0/ - Max Barraclough
@MaxBarraclough:请查看编辑内容,以及这个新问题 - einpoklum
3个回答

24
  • 赋值/放置行为:

    • boost::variant 可能会在将值赋给一个已存在的 variant分配内存。有很多规则来控制这个行为的发生,因此一个 boost::variant 是否会分配内存取决于它被实例化时的 Ts

    • std::variant 绝不会动态分配内存。然而,作为对 C++ 对象复杂规则的让步,如果赋值/放置操作抛出异常,那么 variant 可能进入“valueless_by_exception”状态。variant 在这种状态下无法访问,也不能使用任何其他访问特定成员的函数。

      只有在赋值/放置操作抛出异常时才会进入这种状态。

  • Boost.Variant 包括 recursive_variant,它允许一个 variant 包含它自己。它们本质上是指向 boost::variant 的指针的特殊包装器,但它们与访问机制相结合。

    std::variant 没有这样的辅助类型。

  • std::variant 提供了更多使用 C++11 之后特性的方式。例如:


能否在不改变 std::variant 类的情况下,向 namespace std 添加一些内容,以实现这种递归功能? - einpoklum
@einpoklum:recursive_variant 的重点在于它允许 visit 自动解包并访问它所引用的对象。因此,你必须将这个机制构建到 std::visit 中。但是,这不可能在替换std::visit的情况下完成,而您无法这样做。当然,您可以在自己的命名空间中制作自己的版本的std::visit以进行解包/访问。 - Nicol Bolas
1
@DanNissenbaum:没有针对这个问题提出建议。然而,N4450 (pdf) 显示委员会并不特别支持它。话虽如此,现在它已经成为标准的一部分,这是可以重新审视的。然而,委员会可能会认为更好的是一个通用机制,允许自定义 std::visit 的使用。这样,您就可以编写自己的 variant,使用该机制并进行自己的递归访问。 - Nicol Bolas
2
NicolBolas和阅读本文的任何人-关于递归变体的委员会投票结果是8个中立,4个弱反对,2个强烈反对。而且问题是“需要吗?”而不是“希望?”或“有用?”确实,正如Nicol所建议的那样,随着更多人使用std :: variant,这可能会改变。@DanNissenbaum:您可以在标准邮件列表中提出此问题(甚至撰写实际提案)。 - einpoklum
另一个区别是Boost目前使用带有指针的std::get,而非std::get_if来作为非抛出类型的选择。 - Bruce Adams
显示剩余2条评论

4

在设计变体类时,最有争议的主要问题似乎是当对变体进行赋值(完成后应销毁旧值)时抛出异常时应该发生什么:

variant<std::string, MyClassWithThrowingDefaultCtor> v = "ABC";
v = MyClassWithThrowingDefaultCtor();

选项似乎有:
  • 通过限制可以表示的类型为无抛异常移动构造类型来防止这种情况。
  • 保留旧值 - 但这需要双缓冲。
  • 在堆上构造新值,将指向它的指针存储在变体中(即使出现异常,变体本身也不会被破坏)。显然,这就是boost :: variant所做的。
  • 对于每个变量都有一个'未连接'状态,并在这种故障发生时转到该状态。
  • 未定义行为
  • 当出现此类情况后尝试读取其值时,使变量抛出异常。

如果我没错的话,后者已经被接受。

这是摘自Axel Naumann的ISO C ++博客文章(2015年11月)的摘要。


0

std::variant与boost::variant略有不同

  • std::variant在头文件中声明,而不是在<boost.variant.hpp>中声明
  • std::variant永远不会分配内存
  • std::variant可用于constexpr
  • 对于std::variant,您必须编写std::get_if(&variable),而不是boost::get(&variable)
  • std::variant不能递归地持有自身,并且缺少一些其他高级技术
  • std::variant可以就地构造对象
  • std::variant具有index()而不是which()

Boost.Variant不是C++标准的一部分。 - einpoklum

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