标签: std-variant

`std :: variant` vs.继承vs.其他方式(性能)

我想知道std::variant性能。什么时候不应该使用它?似乎虚拟函数仍然比使用虚拟函数好得多,std::visit这让我感到惊讶!

pattern checking解释std::holds_alternativesoverloaded方法之后,Bjarne Stroustrup在“ C ++之旅”中谈到了这一点:

这基本上等效于虚拟函数调用,但可能更快。与所有性能要求一样,当性能至关重要时,应通过测量来验证“可能更快”。对于大多数用途,性能差异不明显。

我已经确定了一些想到的方法,这些是结果:

基准 http://quick-bench.com/N35RRw_IFO74ZihFbtMu4BIKCJg

如果启用优化,您将得到不同的结果:

启用op的基准

http://quick-bench.com/p6KIUtRxZdHJeiFiGI8gjbOumoc

这是我用于基准测试的代码;我确信,有更好的方法来实现和使用变体来代替虚拟关键字来使用它们(继承与std :: variant):

删除旧代码;看看更新

任何人都可以解释一下实现此用例的最佳方法是什么,这std::variant使我不得不进行测试和基准测试:

我当前正在实现RFC 3986,它是“ URI”,在我的用例中,该类将更多地用作const,并且可能不会做很多更改,并且用户更有可能使用此类来查找每个特定类URI的一部分,而不是生成URI;因此,有意义的是使用std::string_viewURI的每个段,而不是将其分开std::string。问题是我需要为其实现两个类。当我只需要一个const版本时;另一个用于当用户想要创建URI而不是提供一个URI并进行搜索时。

所以我用a template来解决有其自身问题的内容;但是后来我意识到我可以使用std::variant<std::string, std::string_view>(或者也许std::variant<CustomStructHoldingAllThePieces, std::string_view>);所以我开始研究以查看是否确实有助于使用变体。从这些结果看,似乎要使用继承,virtual如果我不想实现两个不同的const_uriand uri类,那是我最好的选择。

您认为我该怎么办?


更新(2)

感谢@gan_在我的基准代码中提到并解决了起吊问题。 基准 http://quick-bench.com/Mcclomh03nu8nDCgT3T302xKnXY

我对try-catch hell的结果感到惊讶,但是由于有了现在有意义的注释

更新(3)

我删除了该try-catch方法,因为它确实很糟糕。而且还随机更改了所选值,并且通过它的外观,我看到了更现实的基准。virtual毕竟这似乎不是正确的答案。 随机访问 http://quick-bench.com/o92Yrt0tmqTdcvufmIpu_fIfHt0

http://quick-bench.com/FFbe3bsIpdFsmgKfm94xGNFKVKs(无内存泄漏大声笑)

更新(4)

我消除了生成随机数的开销(我在上一次更新中已经做到了,但是似乎我抓取了错误的URL作为基准),并添加了EmptyRandom来理解生成随机数的基准。并且对Virtual进行了一些小的更改,但我认为它没有任何影响。 空随机添加 http://quick-bench.com/EmhM-S-xoA0LABYK6yrMyBb8UeI

http://quick-bench.com/5hBZprSRIRGuDaBZ_wj0cOwnNhw(已删除了“虚拟”,因此您可以更好地比较其余虚拟)


更新(5)

正如豪尔赫·贝隆(Jorge Bellon)在评论中说的 …

c++ performance inheritance c++17 std-variant

35
推荐指数
2
解决办法
1167
查看次数

为什么我的结构被 `std::variant` 和 `std::monostate` 破坏了两次?

我在尝试学习std::variant。我不明白为什么在这个例子中,我不想初始化ab,并且我std::monostate为此使用,类A被构造一次,但被破坏两次。怎么了?

#include <iostream>
#include <variant>

struct A
{
    A() { std::cout << "Constructing A\n"; }
    ~A() { std::cout << "Destructing A\n"; }
};


struct B
{
    B() { std::cout << "Constructing B\n"; }
    ~B() { std::cout << "Destructing B\n"; }
};


int main()
{
    std::variant<std::monostate, A, B> ab;
    ab = A();
}
Run Code Online (Sandbox Code Playgroud)

运行此示例会给出以下输出。

Constructing A
Destructing A
Destructing A
Run Code Online (Sandbox Code Playgroud)

c++ c++17 std-variant

25
推荐指数
2
解决办法
2079
查看次数

我可以在对std :: visit的调用中更改std :: variant中的保留类型

以下代码是否会调用未定义的行为?

std::variant<A,B> v = ...;

std::visit([&v](auto& e){
  if constexpr (std::is_same_v<std::remove_reference_t<decltype(e)>,A>)
    e.some_modifying_operation_on_A();
  else {
    int i = e.some_accessor_of_B();
    v = some_function_returning_A(i); 
  }
}, v);
Run Code Online (Sandbox Code Playgroud)

特别是,当变体不包含时A,此代码将重新分配,A同时仍保留对先前持有的type对象的引用B。但是,由于分配后不再使用该引用,因此我觉得代码很好。但是,标准库是否可以以std::visit 上述方式不确定的方式自由实施?

c++ c++17 std-variant

22
推荐指数
1
解决办法
465
查看次数

按给定条件拆分给定 std::variant 类型

如何通过给定的变体类型

using V = std::variant<bool, char, std::string, int, float, double, std::vector<int>>;
Run Code Online (Sandbox Code Playgroud)

声明两种变体类型

using V1 = std::variant<bool, char, int, float, double>;
using V2 = std::variant<std::string, std::vector<int>>;
Run Code Online (Sandbox Code Playgroud)

whereV1包括所有算术类型VV2包括所有非算术类型V

V 可以是模板类的参数,例如:

template <class V>
struct TheAnswer
{
    using V1 = ?;
    using V2 = ?;
};
Run Code Online (Sandbox Code Playgroud)

一般来说,标准可以是这样的constexpr变量:

template <class T>
constexpr bool filter;
Run Code Online (Sandbox Code Playgroud)

c++ c++17 std-variant

20
推荐指数
2
解决办法
650
查看次数

我如何编写类似 std::variant 开关的代码?

我有一些var = std::variant<std::monostate, a, b, c>什么时候a, b, c是一些类型。

如何在运行时检查var包含什么类型?

在官方文档中,我发现如果var包含a类型并且我写的信息,std::get<b>(var)我会得到一个异常。所以我想到了这个解决方案:

try {
  std::variant<a>(var);
  // Do something
} catch(const std::bad_variant_access&) {
  try {
    std::variant<b>(var);
    // Do something else
  } catch(const std::bad_variant_access&) {
    try {
     std::variant<c>(var);
     // Another else
    } catch (const std::bad_variant_access&) {
      // std::monostate
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

但它是如此复杂和丑陋!有没有更简单的方法来检查std::variant包含什么类型?

c++ c++17 std-variant

19
推荐指数
2
解决办法
2863
查看次数

std :: optional如何永远不会“异常无价值”?

std::variant可以进入一种称为“ 异常无价值 ”的状态。

据我了解,这是导致移动分配抛出异常的常见原因。不能保证该变体的旧值不再存在,预期的新值也不是。

std::optional,但是没有这种状态。cppreference提出了大胆的主张:

如果引发异常,则* this ...的初始化状态不变,即,如果对象包含一个值,则它仍然包含一个值,反之亦然。

如何std::optional避免变得“异常无价值”而std::variant不能避免?

c++ c++17 stdoptional std-variant

14
推荐指数
3
解决办法
367
查看次数

std::variant 在 MSVC 和 gcc 中的行为不同

MSVC 19.28 拒绝以下代码,但 gcc 10.2 接受并输出 true false

#include <iostream>
#include <variant>

int main()
{
    std::variant<long long, double> v{ 0 };
    std::cout << std::boolalpha << std::holds_alternative<long long>(v) << ' ' << std::holds_alternative<double>(v) << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

根据cppreference

  1. 转换构造函数。如果同时对作用域中的每个from存在虚函数的重载,则构造一个包含替代类型的变体,该变体T_j将通过重载决议为表达式选择,除了:仅当声明对某些人有效时才考虑重载发明变量;直接初始化包含的值,就像通过从 直接非列表初始化一样。F(std::forward<T>(t))F(T_i)T_iTypes...F(T_i)T_i x[] = { std::forward<T>(t) };xstd::forward<T>(t)

和这个问题被转换成的哪个功能F(long long)F(double)选择agianst参数1由超负荷的分辨率。

转换intlong long整数转换(假设sizeof(long long)大于sizeof(int)),转换intdouble浮点整数转换,两者的排名都不高于另一个。所以调用是模棱两可的,程序格式不正确。

MSVC 确实像我预期的那样拒绝了代码,但令我惊讶的是,gcc …

c++ std-variant

13
推荐指数
1
解决办法
203
查看次数

为什么sizeof(std :: variant <char>)== 8使用libc ++而不是2(比如MSVC的STL和libstdc ++)?

在Compiler explorer上考虑这个例子.

基本上,我们有这个代码片段:

#include <cstdint>
#include <variant>

enum class Enum1 : std::uint8_t { A, B };

enum class Enum2 : std::uint8_t { C, D };

using Var = std::variant< Enum1, Enum2 >;
using Var2 = std::variant< char >;

template< std::size_t s >
struct print_size;

void func() {
    print_size< sizeof( Var ) >{};
    print_size< sizeof( Var2 ) >{};
}
Run Code Online (Sandbox Code Playgroud)

如果我们使用GCC的libstdc ++(使用clang或GCC)编译它,我们会得到预期的编译错误:

error: implicit instantiation of undefined template 'print_size<2>'
Run Code Online (Sandbox Code Playgroud)

此外,与MSVC类似(如预期):

error C2027: use of undefined type 'print_size<2>'
Run Code Online (Sandbox Code Playgroud)

但是,当使用clang与libc ++时,我收到此错误: …

c++ libc++ c++17 std-variant

12
推荐指数
1
解决办法
425
查看次数

std::get_if (std::variant) 通过指针而不是通过值/&amp;/const&amp; 获取变体参数是否有任何实际原因?

我从未使用过std::get_if,并且由于它的名称与 不同std::get,我看不出为什么它的参数应该是指针\xc2\xb9 (而std::get有一个按引用参数)。

\n
\n

\xc2\xb9如果它也被命名std::get,那么重载解析就足够了。

\n
\n

是的,我的问题可能会被欺骗到问题 std::any_cast() 和 std::get_if(std::variant) 是否绝对有必要将指针作为参数?,但关键是没有答案解决std::get_ifvs std::get,只有一个评论;唯一的答案集中在std::any_cast

\n

c++ pass-by-reference pass-by-pointer c++17 std-variant

12
推荐指数
1
解决办法
5814
查看次数

使用 C++ std::visit 时如何改进编译器错误消息?

我在具有std::visit()许多替代方案的变体上使用 C++17 的函数,每当我忘记访问者中的一个或多个替代方案时,编译器生成的错误消息都非常难以理解。

例如

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

using Foo = std::variant<A, B, /* ... many more alternatives ... */>;

Foo foo;

std::visit(overloaded{
    [](A const& a) { /* ... */ },
    [](B const& b) { /* ... */ },
    /* ... forgot 1+ alternatives ... */
    }, foo
);
Run Code Online (Sandbox Code Playgroud)

在上面的代码示例中,编译器可以生成长度为数千个字符的错误消息,具体取决于替代方案的数量。有没有办法改进这些错误消息,以便编译器输出类似以下内容?

example.cc:8-13: error: Non-exhaustive visitor -- missing alternative of type 'X'
Run Code Online (Sandbox Code Playgroud)

c++ compiler-errors std-variant

10
推荐指数
1
解决办法
1101
查看次数