假设我正在编写一个将传递std::variant<A,B>
给另一个类的类。
在设计类时,我决定保留传递给另一个类的最后一个内容的副本。由于启动时尚未传递任何内容,因此将其存储为 对我来说是有意义的std::optional<std::variant<A,B>>
。
但std::monostate
确实存在,而且我的印象是,它是专门为在这种情况下使用而创建的(此处用std::variant<std::monostate,A,B>
)
问题是,我不明白为什么我应该使用它:
- 在我看来,它不如使用
std::optional
- 我不想传递另一个函数 a
std::variant<std::monostate,A,B>
(否则它必须处理不包含任何内容的情况),但据我所知,我无法将对象分配std::variant<A,B>
给std::variant<std::monostate,A,B>
变量,所以我必须手动处理转换
那么,如果这里不适用,那么我什么时候应该使用std::monostate
而不是使用std::optional
with std::variant
,为什么?或者我是否错过了一些可以让它在这里成为好选择的东西?
2
最佳答案
2
std::monostate
我个人在存储可能为空的变体时使用,使用std::optional
额外的 8 个字节,因此如果您有许多这种变体,那么这些字节就会加起来。
我有一个例子是,DragLogic
当无法拖动时,它可能为空,它是多种拖动模式的变体,或者std::monostate
如果无法拖动。
至于std::optional<std::variant<>>
这可以是函数的参数或返回类型,但存储可选项是浪费的,由 8 个可选项组成的对象浪费了多达 56 个字节的填充,因为每个 bool 在 64 位系统上占用 8 个字节而不是 1 个字节,对于std::variant
可以表示空状态的指针来说,这种浪费是不必要的,对于可以为空的指针也可以这样说。
6
-
如果我错了请纠正我,但是 a 的大小
std::optional
是指针的大小,而 a 的大小std::variant
是它可以容纳的最大类型的大小,对吗? 如果是这样,那是否意味着对于任何大于指针的类型,使用所需的空间都更少std::optional
?
– -
3@Eternal a 的大小
std::optional<T>
是 的大小T
加上 的大小bool
再加上任何适用的填充。
– -
1
is that of a pointer
不,它是包含的类型的大小 + 表示是否已接合的 bool。还请记住填充。
–
-
1@Eternal 正确。a 的大小
std::optional<char>
应该是2
。 “std::variant 的大小是它可以容纳的最大类型的大小,对吗?” 不。它是最大类型的大小,加上索引其真实类型的大小std::size_t
,再加上任何填充。a 的大小std::variant<char>
可能是1
(char) 加上8
(size_t) 加上7
(填充)。
–
-
1@DrewDormann 仅供参考,的大小
std::optional<T>
不由 T 以外的任何因素决定。它“至少是 T 的大小”,但“是否参与”的存储方式不在标准范围内。它可以存储在原始 T 的填充字节中,可以是布尔值,可以是可选本身中的某个神奇位或简单的 int。因此说“T 的大小加上 bool 的大小”是错误的。
–
|
至于monostate
(稍微离题):明确指出,它旨在用于需要默认构造的非默认可构造变体。
考虑以下代码:
struct S { explicit S(int){} };
struct Z { explicit Z(char){} };
S getS();
Z getZ();
// (...)
void f()
{
std::variant<std::monostate, S, Z> vsz;
if (//whatever) {
vsz = getS();
}
else {
vsz = getZ();
}
}
在这段代码中,如果没有monostate
,它将无法编译,因为该变体默认初始化其最左边的类型。当然,这段代码可以重写为不存在问题的形式。但其他代码可能不行。
此外,std::optional<std::variant<S, Z>>
这里当然也可以。但是,可选的布尔标志会带来额外的开销。这是否对您的特定情况很重要是一个悬而未决的问题。
monostate
但是,没有这种开销。
从 API 角度来看,取消引用空变量可能稍微困难一些。当然,人们总是可以这样做,*get_if<X>(&v);
而不是访问或抛出 API,但它仍然比普通的更容易发现*x
。
也可以使用 来实现吗optional
?部分可以。在一定程度上,使用 C++23 单子接口,但没有什么(除了编码标准)可以阻止程序员在其上调用 * 运算符。不过,使用一些替代品是可能的,比如 Sy Brand 的tl::optional
。
总而言之,应该使用什么是设计决策;它可以根据个人喜好(我注意到我的同事在交替使用 C++ 和 Rust 时倾向于使用变体和访问而不是 switch-case、可选项等,但这只是我个人的观察,YMMV)、公司建立的编码指南、库可用性(也许不欢迎外部依赖?)和其他各种因素来制定。
2
-
1
std::optional<std::variant<S, Z>> vsz;
也可以… OP 想要比较std::optional
,而不是有或没有的变体std::monostate
– -
@Jarod42 我重新措辞了我的答案,谢谢。
–
|
std::monostate
变体不会增加其大小。放入变体std::optional
总会增加其大小。–
std::monostate
使默认可构造。std::variant
–
|