如何高效返回std::optional?关于std::make_optional的优化疑问
高效返回std::optional并使用std::make_optional的优化问题
示例代码
std::optional<Path> CreateCanonicalPath(const std::string_view& path) { std::error_code errorCode; const auto result = std::filesystem::weakly_canonical(std::filesystem::u8path(path), errorCode); return !errorCode ? std::make_optional(result) : std::nullopt; }
核心疑问
- 将
result传入std::make_optional是否存在优化空间? - 使用
std::make_optional(std::move(result))是否更优? - 手动
std::move是否会阻止RVO或NVRO?
解答
1. 先解决关键前提:const修饰的result无法被移动
原代码中result被const修饰,此时调用std::move(result)会得到const Path&&类型的右值,而标准库多数移动构造函数仅接受非const右值引用,这会导致std::move退化为拷贝操作——相当于没加std::move。所以第一步必须去掉const,才能让移动操作生效。
2. 手动std::move确实更优(当Path移动开销低于拷贝时)
result是函数内的局部变量,但它并非直接作为return语句的返回值,而是作为参数传递给std::make_optional。这种场景下,编译器无法触发NVRO(命名返回值优化)——因为返回的是std::make_optional创建的std::optional<Path>临时对象,而非result本身。
如果Path类型包含堆内存、文件句柄等资源,其移动构造开销远低于拷贝构造,手动添加std::move可以避免一次不必要的拷贝,直接将result的资源转移到std::optional内部存储的对象中,有效提升效率。
3. 手动std::move不会阻止RVO/NVRO
RVO/NVRO针对的是直接返回局部变量的场景,而本函数返回的是std::make_optional生成的临时std::optional<Path>对象。编译器依然可以对这个临时对象触发RVO(返回值优化),直接将其构造到函数的返回值内存位置,不会因为std::move(result)而被阻止。
优化后的代码
std::optional<Path> CreateCanonicalPath(const std::string_view& path) { std::error_code errorCode; auto result = std::filesystem::weakly_canonical(std::filesystem::u8path(path), errorCode); return !errorCode ? std::make_optional(std::move(result)) : std::nullopt; }
补充说明
- 如果
Path是轻量类型(仅包含栈上成员),拷贝和移动开销几乎一致,此时加不加std::move区别不大,但添加也不会带来负面影响。 - 若
Path的移动构造函数标记为noexcept,std::optional在构造时会利用这一点,避免额外的异常安全检查,进一步提升性能。
内容的提问来源于stack exchange,提问作者Jiří Lechner
相关产品推荐
相关产品推荐

