std::optional 默认构造要求 T 可默认或零初始化,故无默认构造函数的类型直接声明会失败;emplace 通过 placement new 原位构造,绕过该限制,是唯一支持不可默认构造、不可移动类型的初始化方式。
std::optional 本身不要求 T 有默认构造函数,但你必须用初始化它;任何其他方式(包括、或直接初始化)在 T 无默认构造时都会编译失败。
为什么
声明就报错?
这不是
的问题,而是默认构造
这一步就卡住了。标准要求其内部 union 存储需能“容纳”一个
,而默认构造时会尝试对
做值初始化——这隐式依赖
可默认构造或可零初始化。
没有默认构造函数
→ 编译失败:error: use of deleted function ‘S::S()’
→ 合法:跳过对
的初始化
→ 同样合法
是唯一能绕过默认构造限制的入口
不走默认构造 + 赋值的老路,而是直接在
预留的未初始化内存上用
构造对象。它只关心你传的参数能否匹配
的某个构造函数,完全不碰
的默认构造函数。
✅ 直接调用
❌ 先构造临时
,再移动赋值 → 要求
可移动,且赋值路径内部仍可能触发默认构造检查
❌ 编译失败:模板推导不支持构造参数包,且它仍要先构造临时对象
⚠️ 错误:又引入了临时对象,失去原位意义
赋值 vs
:性能和语义差异很实在
即使
有默认构造,
也比赋值更轻量——它省掉一次临时对象生命周期,这对重型类型(如含资源句柄、大缓冲区、不可移动类)是实打实的收益。
C知道
CSDN推出的一款AI技术问答工具
下载
立即学习
“
C++免费学习笔记(深入)
”;
赋值路径:
→
→ 移动构造 →
路径:直接
,仅一次构造
对
、
等禁用拷贝/移动的类型,
是唯一可行方式
若
已含值,
会先调用旧值析构函数 —— 确保
是
,否则行为未定义
容易被忽略的边界情况
成功返回不等于值一定可用;它不检查构造函数是否抛异常,也不提供链式安全保证。
若
的构造函数抛异常,
会回到
状态(旧值已析构,新值未构造),但不会通知你
调用后务必用
或
确认状态,别假设“调用了就一定有值”
不接受
字面量写法:
可能不调用初始化列表构造函数;应显式写
参数转发是完美的:传
就转发为右值引用,传
就匹配
,但括号多套一层(如
)可能引发重载歧义
emplace=std::make_optionalstd::optionalemplacestd::optionalTTTstruct S { S(int) {} };std::optional opt;std::optional opt{std::nullopt};Sstd::optional opt = std::nullopt;emplaceemplaceoptionalplacement newTTopt.emplace(42);S(42)opt = S(42);S(42)Sstd::make_optional(42);opt.emplace(S(42));emplaceTemplaceT temp(arg...)optional::operator=(T&&)temp.~T()emplace::new (storage) T(arg...)std::mutexstd::atomicemplaceoptionalemplaceT::~T()noexceptemplaceToptionalnulloptopt.has_value()if (opt)emplacestd::initializer_listopt.emplace({1,2,3})opt.emplace(std::initializer_list{1,2,3}) std::string&&const char*std::string(const char*)(("hello"))