提问人:NoSenseEtAl 提问时间:10/15/2023 最后编辑:EvgNoSenseEtAl 更新时间:10/16/2023 访问量:167
为什么 std::optional 不使用 sentinel 值来表示空的可选?
Why does std::optional not use a sentinel value to represent an empty optional?
问:
我知道这艘船由于需要的 ABI 破损而航行,但我想知道为什么最初实现没有决定使用一些魔术位模式来 , , 等......表示空可选值。这显然需要在类型的位表示中有一些“自由”值,因此例如它不适用于 ,但许多复杂类型具有某些表示形式,这些表示形式在有效实例中永远不会发生。std::string
std::vector
int
size_t
C++甚至可以指定某种方式来选择你的类型,例如通过specialing
std::nullopt_sentinel
用于用户定义的类型。
我最好的猜测是,指定/实现这一点的复杂性(因为您需要为许多类型实现哨兵)比始终使用布尔值要大得多,但我想知道是否还有其他原因。std::
Rust 的类似答案似乎有时会实现这一点
答:
标准库和任何专门针对自己类型的用户已经可以自由地以这种方式实现它(参见 [namespace.std] p2)。std::optional
在强制执行的规范中,没有任何内容使用标志来指示值的存在。如何识别空状态是一个实现细节。如果可选为空,则用户不能依赖于观察有关内部状态的任何其他内容。std::optional
bool
std::optional
评论
optional
std
std::optional
)
std::numeric_limits
std::hash
假设一个类型可以识别它具有表示“不是值”的“魔术位模式”。比方说,根据是否宣传这一点,将以不同的方式实现。optional<T>
T
那么这里有一个问题:这个“魔术位模式”实际上是一个有效的类型值吗?T
如果这个问题的答案是“是”,那么我们就有问题了。因为它是 type 的有效值,所以用户可以创建具有该值的类型的对象,对吧?所以。。。如果他们这样做会怎样?被认为是“已参与,但包含价值”还是被认为是未参与?T
T
optional<T> t(invalid_value);
t
t
optional
如果是前者,那么如何辨别和的区别呢?这两个 s 的位模式是相同的,因此它们的行为必须相同,对吧?optional<T>
optional<T> t{}
optional<T> t(invalid_value);
t
所以如果是后者,那么你告诉我这个代码:
T t = something();
optional<T> ot(t);
if(ot)
{
//Do something with ot
}
你有没有可能永远不会根据什么回报做某事?这是荒谬的。从代码的结构来看,你给它一个 ,如果你给它一个,它应该存储一个。这就是该类型的用途。ot
something
t
optional<T>
T
所以,回到问题,现在必须认为这个“魔术位模式”不是 type 的有效值。也就是说,您无法创建具有此“魔术位模式”的模式。T
T
现在,您如何实现这一点?当你构造一个未参与的,它必须将存储视为只是一袋比特。它必须使用从某个基于接口获取的值来初始化这组位。然后,为了测试是否接合,它必须将所有这些位与未接合的位进行比较。optional<T>
T
T
optional<T>
现在,这要求它不能轻易复制。或者微不足道地默认可构造。否则,可能会创建具有无效值的 a。T
T
但更重要的是,你甚至错过了想要这个的关键原因之一:空间效率。
看,大多数“许多复杂类型[它们]具有某些表示形式,而这些表示形式永远不会出现在有效实例中”,这些类型都比 .即使是 a 的大小也是 a 的 3 倍(包括 64 位指针的填充),更不用说小字符串优化了。由于这些类型中的大多数都比用于表示它们的跟踪数据大得多,因此空间效率的论点并不是特别令人信服。bool
vector<int>
bool
std::string
大多数情况下,需要这种空间效率的情况是不可为 null 的指针或枚举器之类的东西。也就是说,您希望 null 指针值像未参与的 ,并且您希望不是枚举器之一的枚举值像未参与的 。这些是小类型,与 的大小相当,因此当前的实现会将其大小增加一倍。节省空间非常大。optional<T*>
optional<E>
bool
optional
越大,节省空间的意义就越小。T
但你会注意到另一件事。越大,测试是否未接合所需的时间就越长。如果大小为 24 字节,则必须比较 3 个 8 字节的字以查看它是否参与。T
optional<T>
T
同样,只有在很小的情况下才有意义。越大,情况越糟。T
T
评论
std::vector
std::string
int,size_t
“ 你似乎已经回答了自己的问题。
std::optional::empty()
std::string
operator*
std::optional
std::optional
std::string