Skip to content

std::optional(C++17)

面试高频新特性组件。表示「可能存在的值」:要么含有值,要么为空(无值)。学会讲清「为什么需要、底层怎么存、怎么用、和指针/其它类型的区别」。

一、为什么需要 optional?

此前表达「可能没有结果 / 无效值」的办法都有缺点:

  • 用指针判空:不安全、易悬垂、所有权语义混乱;
  • 返回 -1/哨兵值:依赖约定、容易漏判、对非整数类型不优雅;
  • 抛异常:对「正常无值」情况过重。

optional<T> 语义清晰:「这个 int 可能没有」,读取前 has_value() / 安全访问即可。

cpp
#include <optional>
std::optional<int> find(const std::string& s, char c); // 找不到返回 nullopt

auto r = find("abc", 'x');
if (r) {                // 或 r.has_value()
    int v = r.value();
} else {
    // 无值分支
}

二、无值构造与安全的读取

cpp
std::optional<int> a;                 // 默认无值
std::optional<int> b = std::nullopt;  // 显式无值
std::optional<int> c = 42;            // 有值

访问 API:

方法无值时
operator* / operator->UB(必须先判空)
value()std::bad_optional_access
value_or(fallback)返回 fallback
has_value() / operator bool判空用
emplace(a...) / reset()构造/清空

三、底层实现(面试常问「optional 是怎么实现的」)

不堆分配 —— T 直接内联存储于 optional 内部。核心思想同手写容器:原始存储 + 按需构造 + 标记是否有值

cpp
template <typename T>
class optional {
    union {
        // 用 union/字节数组预留存放 T 的空间,仅在需要时 placement new
    };
    bool has_value_;
    // 内存布局 ≈ alignas(合适的存储) + bool
};
  • 它用 placement new 就地构造,析构时若 has_value 才调 ~T()
  • 空 optional 大小通常 = sizeof(T)(或 T+1 以放标志,受实现/对齐影响),不额外堆分配,比 unique_ptr 存动态对象更省。

因此手写 optional 的关键实现点

  1. 用原始字节/对齐 buffer 保存潜在 T,不立刻构造默认 T。
  2. 构造/复制/移动/析构都判断 has_value 后再操作底层 T。
  3. 提供 value_or/value 等异常安全的读取。

四、高频问答

Q1. optional<T&> 支持吗?

C++17 的 optional 是 optional<T>不支持引用类型 T(引用无法存进 union/需要 rebind 语义)。若想存「可空的引用」,用 optional<std::reference_wrapper<T>> 或指针。

Q2. optional vs unique_ptr/shared_ptr?

  • shared/unique 表达「所有权 + 可能为空 + 动态对象」,需堆分配。
  • optional 表达「值本身可有可无」且对象就地存放,通常更快(无堆分配)。
  • 语义不同:optional 不是「指向对象的指针」,是「一个可能没值的值」。

Q3. optional 什么时候不该用?

  • 若「空」代表严重错误并需要调用方能识别原因 → 用异常/错误码,不吞成普通空值。
  • 大量频繁判断空值导致代码啰嗦时,也许更适合「值对象 + 哨兵」或直接返回可空指针(视场景)。

Q4. 与 std::variant/std::any 区别

  • optional:T 有/无;
  • variant:同时可能为 T1/T2/…(sum type);
  • any:运行期任意类型(类型擦除)。 见 stl/string-and-optional.md(已整理对比表)。

五、手写思路 15 秒版

字节 buffer + has_value 标志;构造:new (&buf_) T(args...) 置 has;析构有值才 ~T()value() 判 has 否则抛;operator* 不加检查(同 std 语义)。

C++ 面试八股 · VitePress 版