std::variant与std::any在类型具有移动构造函数时的区别

6
如果我有一个类型是std::is_nothrow_move_constructible,并且需要将其存储在std::anystd::variant中,您会建议使用哪个?为什么?哪一个会产生最少的开销?编辑:在使用std::variantstd::any方面有不同的用例吗?
class MyType
{
public:
   MyType(const MyType&) = default;
   MyType(MyType&&) = default;
   MyType() = default;
};

int main(int argc, char* argv[])
{
   static_assert(std::is_nothrow_move_constructible<MyType>::value, "Not move constructible");
   return 0;
}

10
它们被用于不同的目的。 - Rinat Veliakhmedov
3
当您知道可以存储哪些类型时(基本上是更安全的联合),请使用std::variant,当您不知道时,请使用std::any。 - ralismark
1
扩展@ralismark的观点:std::any基本上是围绕void*包装有用操作的封装。 - Caleth
1
对于 std::variant,您需要枚举可能的类型。如果您知道所有这些类型,那么最好,因为您能够处理它们所有。 (参见:variant::visit())但是,对于 std::any,您应该为“意外”类型提供默认情况。 - titapo
1
任何和变量执行完全不同的操作。使用最适合您问题的模型。 - Kerrek SB
显示剩余3条评论
3个回答

9
如果您知道类型,请使用 std::variant。在这种情况下,您知道所有可能的类型,并使用 std::variant::visit() 将访问者应用于变体。
如果您不知道,请使用 std::any。当处理通用类型时,您可以考虑这一点。换句话说,当类型不是先验已知时。

哪一个会产生最少的开销?

std::variant,不能使用堆。这是不允许的。如果静态声明包含可能变量的结构,则没有实际方法可以将其自身的可能变体包含在其中,因为这样的结构可以很容易地显示为无限大小。来源

相比之下,std::any 可能会这样做。因此,第一个将具有更快的构建和复制操作。


我很好奇,如果你甚至不知道void*或者std::any的可能类型,那么你如何使用它们呢? - Passer By
@PasserBy 请查看此示例。不过我理解你的观点。也许我的回答中的这部分内容会让读者感到困惑,应该将其删除。你认为呢? - gsamaras
从这个例子来看,我认为使用std::any更有说服力的原因是它使用可变数量的内存,因为类型(显然)最终已知。 - Passer By
在我的例子中,我提到了一种不会抛出异常的移动构造类型,根据cppreference,在std::any中不应该为这些类型分配堆内存。 “鼓励实现避免为小对象进行动态分配,但是这种优化只能应用于std::is_nothrow_move_constructible返回true的类型。” 那么在这种情况下,哪一个会产生更多的开销? - 0xBADF00
@0xBADF00 我更新了我的回答。就像我说的,我同意你所说的,但总的来说我的回答是正确的。 - gsamaras
std::any不能与非可复制类型一起使用。没有构造函数或emplace调用不需要is_copy_constructible并将要存储的类型作为参数。因此,这并没有回答这个问题... - Jaime

2

std::any会从堆上分配存储空间来存放值。而std::variant不使用堆,因此在构造和复制方面更有效率,并且更加缓存友好,因为值直接存储在对象中,不涉及间接寻址和虚函数来访问它。

std::variant的接口更丰富,更易于使用。

std::any仅在由于某些原因无法使用std::variant时才有用。


1
@PasserBy 您的陈述是错误的,访问不需要使用任何虚函数。 - Maxim Egorushkin
1
@PasserBy 这很简单:switch(variant.index()) { case 0: visitor(std::get<0>(variant); break; ...}。没有涉及到虚函数。std的实现更复杂,可以查一下,但仍然没有使用虚函数。 - Maxim Egorushkin
1
这是否真的属实:“std::any 从堆上分配了值的存储空间”?就我所理解的,对于那些无需抛出异常的移动构造小型类型,它不应该这样做。 - 0xBADF00
1
@0xBADF00 可能有一个小值优化,与 std::string 一样,它可以进行小字符串优化。然而,在一般情况下,它会从堆中分配存储空间。 - Maxim Egorushkin
1
@PasserBy 我不确定他们使用查找表实现的原因。然而,我坚持我的观点:variant 实现中没有涉及任何虚函数,值存储在对象内联中。而对于 any,任何对值的访问都涉及虚调用。 - Maxim Egorushkin
显示剩余5条评论

2
出于类型安全、松耦合和易维护的原因:
如果可能,建议使用variant而不是any。
因为本质上,std::any是在可克隆的std::unique_ptr<void, some_deleter>周围进行的糖衣包装。
它有一些保护措施来防止错误转换(它会抛出异常而不是崩溃,在许多程序中这意味着相同的事情)。

错误。使用deleter是实现细节。不适当地使用名义类型会泄漏底层抽象。由于错误使用的副作用,在实践中可能会遭受ABI问题的影响。 - FrankHB
@FrankHB 或许我应该说“语义上等同于”而不是“本质上是一个”。 - Richard Hodges
那通常仍然是错误的。它们在语义上是等价的,嗯,在抽象的哪个级别上?如果有的话,“本质”可以是像“类型擦除对象”之类的东西,但这不能直接由C++表达。等价性仅在编写C++代码之前发生,在此之前,两者都不是对方的糖。 - FrankHB

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