让std::expected调用类私有构造函数:设友元仍编译出错的问题
问题与解决方案
问题背景
我想要创建一个不可拷贝、不可移动的类,对象只能通过create静态函数构造(而非公有构造函数),create返回std::expected对象:要么持有构造完成的类实例,要么持有错误码。为了强制通过create构造,我把所有构造函数设为私有,核心目的是实现无异常的对象构造。
我尝试将std::expected<Foo, std::error_code>设为友元,允许它调用私有构造函数,但编译依然报错。示例代码如下:
class Foo { int val_{}; public: Foo() = delete; Foo(const Foo&) = delete; Foo& operator=(const Foo&) = delete; static auto create(int val) noexcept -> std::expected<Foo, std::error_code> { if (val == 42) return std::unexpected<std::error_code>(std::in_place, EINVAL, std::generic_category()); return std::expected<Foo, std::error_code>{ std::in_place, val }; } private: friend class std::expected<Foo, std::error_code>; explicit Foo(int val) noexcept : val_{ val } {}; };
错误原因
问题出在STL对std::expected的内部实现:直接友元std::expected<Foo, std::error_code>并不足够,因为实际负责构造Foo对象的是std::expected内部的辅助类(比如libstdc++中的__expected_storage,或其他STL实现里的类似内部结构),而非std::expected本身。这些内部类不在友元列表中,因此无法访问Foo的私有构造函数。
解决方案
最稳妥且跨平台的方案是使用标签分派(Tag Dispatch),既保留构造的私有性,又不需要依赖STL的内部实现:
修正后的代码
#include <expected> #include <system_error> class Foo { int val_{}; // 私有标签类型,仅Foo内部可访问 struct ConstructorTag {}; public: Foo() = delete; Foo(const Foo&) = delete; Foo& operator=(const Foo&) = delete; // 禁止移动操作 Foo(Foo&&) = delete; Foo& operator=(Foo&&) = delete; static auto create(int val) noexcept -> std::expected<Foo, std::error_code> { if (val == 42) return std::unexpected(std::error_code{EINVAL, std::generic_category()}); // 传递私有标签,调用构造函数 return std::expected<Foo, std::error_code>{std::in_place, ConstructorTag{}, val}; } // 公有构造函数,但必须传入私有标签才能调用 explicit Foo(ConstructorTag, int val) noexcept : val_{val} {} };
方案优势
- 完全不依赖STL内部实现,跨平台兼容性好
- 依然能强制外部只能通过
create函数构造Foo对象 - 实现了无异常的构造逻辑,符合最初的设计目标
内容的提问来源于stack exchange,提问作者k.st.
相关产品推荐
相关产品推荐

