C++:工厂函数构造std::optional<T>并保留RVO(私有构造无密钥)
问题背景
现有如下代码模式:
class Widget { private: explicit Widget(Dependency&&); public: static std::optional<Widget> make(std::filesystem::path p) { std::optional<Dependency> d = loadDependencyFromPath(p); if (!d.has_value()) { return std::nullopt; } return Widget{std::move(*d)}; } };
这段代码可正常运行,且Widget的Widget(Dependency&&)为私有显式构造函数,用户必须通过静态工厂函数make创建实例,符合需求。但该make函数直接返回Widget对象而非std::optional<Widget>,无法享受C++17的**保证复制消除(RVO)**特性。
尝试将make函数修改为符合RVO要求的写法:
static std::optional<Widget> make(std::filesystem::path p) { std::optional<Dependency> d = loadDependencyFromPath(p); if (!d.has_value()) { return std::nullopt; } return std::make_optional<Widget>(std::move(*d)); }
但因Widget的构造函数为私有显式,编译报错:
error: no matching function for call to 'make_optional<Widget>(std::remove_reference<Dependency&>::type)' 19 | return std::make_optional<Widget>(std::move(*d)); | ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~
使用return std::optional<Widget>(std::in_place, std::move(*d))也会出现相同错误,将std::optional<Widget>设为友元同样无法解决。
已知密钥模式(passkey idiom)可作为替代,但该方案需将构造函数设为公有并添加易混淆的key<T>参数,影响库的用户体验,因此不接受该方案。
技术问询
如何在工厂函数中就地构造std::optional<T>,同时保留C++17保证复制消除(RVO)的资格,且T拥有私有显式构造函数,不使用密钥模式?
解决方案
核心思路是让std::optional能够合法访问Widget的私有构造函数,同时保证返回std::optional<Widget>时触发C++17的保证复制消除。以下是两种可行方案:
方案1:友元std::optional::emplace成员函数
通过将std::optional<Widget>的emplace模板函数设为友元,允许其就地构造Widget实例。工厂函数中创建局部std::optional<Widget>对象,调用emplace完成构造后返回,C++17会保证对该局部对象执行RVO,避免不必要的拷贝或移动。
代码示例:
#include <optional> #include <filesystem> // 假设的依赖定义 struct Dependency {}; std::optional<Dependency> loadDependencyFromPath(std::filesystem::path) { return Dependency{}; } class Widget { private: explicit Widget(Dependency&&); // 友元std::optional的emplace模板,仅允许其调用私有构造 template<typename... Args> friend void std::optional<Widget>::emplace(Args&&... args); public: static std::optional<Widget> make(std::filesystem::path p) { std::optional<Dependency> d = loadDependencyFromPath(p); if (!d.has_value()) { return std::nullopt; } std::optional<Widget> result; result.emplace(std::move(*d)); return result; } }; Widget::Widget(Dependency&&) {}
方案2:友元std::optional的in_place构造函数
直接友元std::optional<Widget>的std::in_place_t版本构造函数,这样可以直接在返回语句中就地构造std::optional<Widget>,同样能触发RVO。
代码示例:
#include <optional> #include <filesystem> struct Dependency {}; std::optional<Dependency> loadDependencyFromPath(std::filesystem::path) { return Dependency{}; } class Widget { private: explicit Widget(Dependency&&); // 友元std::optional的in_place构造模板 template<typename... Args> friend std::optional<Widget>::optional(std::in_place_t, Args&&... args); public: static std::optional<Widget> make(std::filesystem::path p) { std::optional<Dependency> d = loadDependencyFromPath(p); if (!d.has_value()) { return std::nullopt; } return std::optional<Widget>(std::in_place, std::move(*d)); } }; Widget::Widget(Dependency&&) {}
方案说明
- 两种方案均不会暴露
Widget的构造函数给普通用户,依然严格保证只能通过make工厂函数创建实例。 - 返回局部
std::optional对象或直接构造std::optional返回,都符合C++17保证复制消除的条件,不会产生额外的拷贝/移动开销。 - 友元声明针对
std::optional<Widget>的特定成员,不会过度放宽访问权限,安全性可控。
内容的提问来源于stack exchange,提问作者Vittorio Romeo

