为什么访问空 std::optional 时没有 throw 或 sigsegv?

Why there is no throw or sigsegv while accessing empty std::optional?

例子:

#include <optional>
#include <iostream>

using namespace std;

int main()
{
    optional<int> t{}; // nullopt (empty) by default

    cout << *t << endl;

    return 0;
}

实际上这个程序打印了一些 int(int 类型的未初始化值)。 此外,libcxx 使用 assert-check 来访问未使用的值。

为什么标准不需要在这里抛出或 sigsegv?

Why the Standard does not require throwing or sigsegv here?

因为要求某些特定行为隐含地要求添加一个分支来检查该行为 - 无论是抛出还是其他 - 是否应该发生。

通过指定行为未定义,该标准允许实现 not 在每次间接访问时检查可选是否为空。分支执行可能比不分支慢。

委员会没有强制要求安全,而是让标准库实施者选择性能(和简单性)。您测试的实现似乎已选择不抛出异常或以其他方式通知您错误。

因为它是未定义的行为,[optional.observe]p5 部分说:

Requires: *this contains a value.

并且违反 requires 子句是未定义的行为,来自 [res.on.required#1]p1 which is under Library-wide requirements:

Violation of any preconditions specified in a function's Requires: element results in undefined behavior unless the function's Throws: element specifies throwing an exception when the precondition is violated.

所以你对结果没有期望。来自undefined behavior的定义:

behavior for which this document imposes no requirements

要求执行检查会产生成本,并非所有用户都愿意承担该成本。因此,这成为实施质量问题。一个实现可以自由地在不同的操作模式下执行检查,例如在启用断言时。

用户可以选择通过 has_value or value_or. If the user wants an operation that can throw they can use value 自行承担费用。

注意 sigsegv, segfaults etc... are an implementation defined behavior.

C++ 包含未定义行为的思想。

并非所有 C++ 操作都具有标准定义的行为。这允许编译器假设它们永远不会发生,并且在许多情况下可以产生更快的代码。

此处,通过保留未定义未使用的 std::optional 的结果,访问存储在 std::optional 中的数据的成本与访问未存储在 std::optional 中的数据的成本相同一个std::optional。唯一的成本是需要额外的空间,而作为程序员的你承诺会跟踪它是否被占用。

现在编译器可以自由插入检查,有些在调试版本中也可以。

请注意,通常 C++ std 库类型包括 safeunsafe 方法来访问数据。

无效指针有时会导致 sigsev 的事实是因为大多数 OS 保护 0 附近的地址并使访问它的程序崩溃。这是因为它的成本很低,而且它从许多汇编程序、C 和 C++ 程序中捕获了一堆不良行为。

如果你想可选的在空时抛出,使用.value()。如果不这样做,请使用 operator*。如果您想要默认值(如果不存在),请使用 .value_or.