我想知道std::variant性能。什么时候不应该使用它?似乎虚拟函数仍然比使用虚拟函数好得多,std::visit这让我感到惊讶!
在pattern checking解释std::holds_alternatives和overloaded方法之后,Bjarne Stroustrup在“ C ++之旅”中谈到了这一点:
这基本上等效于虚拟函数调用,但可能更快。与所有性能要求一样,当性能至关重要时,应通过测量来验证“可能更快”。对于大多数用途,性能差异不明显。
我已经确定了一些想到的方法,这些是结果:
http://quick-bench.com/N35RRw_IFO74ZihFbtMu4BIKCJg
如果启用优化,您将得到不同的结果:
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类,那是我最好的选择。
您认为我该怎么办?
感谢@gan_在我的基准代码中提到并解决了起吊问题。
http://quick-bench.com/Mcclomh03nu8nDCgT3T302xKnXY
我对try-catch hell的结果感到惊讶,但是由于有了现在有意义的注释。
我删除了该try-catch方法,因为它确实很糟糕。而且还随机更改了所选值,并且通过它的外观,我看到了更现实的基准。virtual毕竟这似乎不是正确的答案。
http://quick-bench.com/o92Yrt0tmqTdcvufmIpu_fIfHt0
http://quick-bench.com/FFbe3bsIpdFsmgKfm94xGNFKVKs(无内存泄漏大声笑)
我消除了生成随机数的开销(我在上一次更新中已经做到了,但是似乎我抓取了错误的URL作为基准),并添加了EmptyRandom来理解生成随机数的基准。并且对Virtual进行了一些小的更改,但我认为它没有任何影响。
http://quick-bench.com/EmhM-S-xoA0LABYK6yrMyBb8UeI
http://quick-bench.com/5hBZprSRIRGuDaBZ_wj0cOwnNhw(已删除了“虚拟”,因此您可以更好地比较其余虚拟)
正如豪尔赫·贝隆(Jorge Bellon)在评论中说的 …
我在尝试学习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) 以下代码是否会调用未定义的行为?
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
上述方式不确定的方式自由实施?
如何通过给定的变体类型
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包括所有算术类型V和V2包括所有非算术类型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) 我有一些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包含什么类型?
std::variant可以进入一种称为“ 异常无价值 ”的状态。
据我了解,这是导致移动分配抛出异常的常见原因。不能保证该变体的旧值不再存在,预期的新值也不是。
std::optional,但是没有这种状态。cppreference提出了大胆的主张:
如果引发异常,则* this ...的初始化状态不变,即,如果对象包含一个值,则它仍然包含一个值,反之亦然。
如何std::optional避免变得“异常无价值”而std::variant不能避免?
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:
- 转换构造函数。如果同时对作用域中的每个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由超负荷的分辨率。
转换int为long long整数转换(假设sizeof(long long)大于sizeof(int)),转换int为double浮点整数转换,两者的排名都不高于另一个。所以调用是模棱两可的,程序格式不正确。
MSVC 确实像我预期的那样拒绝了代码,但令我惊讶的是,gcc …
基本上,我们有这个代码片段:
#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 ++时,我收到此错误: …
我从未使用过std::get_if,并且由于它的名称与 不同std::get,我看不出为什么它的参数应该是指针\xc2\xb9 (而std::get有一个按引用参数)。
\xc2\xb9如果它也被命名std::get,那么重载解析就足够了。
是的,我的问题可能会被欺骗到问题 std::any_cast() 和 std::get_if(std::variant) 是否绝对有必要将指针作为参数?,但关键是没有答案解决std::get_ifvs std::get,只有一个评论;唯一的答案集中在std::any_cast。
我在具有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)