如何在T禁用拷贝/移动构造且构造函数私有时返回std::expected<T,E>
解决私有构造函数下
std::expected的构造问题 问题分析
当把Display接收wl_display*的构造函数设为私有后,create方法里的return {ptr}会报错,原因是std::expected不是Display的友元,无法访问私有构造函数来创建内部的Display实例。已知添加移动构造可以解决,但以下是不需要移动构造的替代方案:
方案1:将std::expected<Display, Error>声明为Display的友元
直接让std::expected拥有访问Display私有成员的权限,这样它就能直接调用私有构造函数来就地构造内部对象:
class Display { private: struct wl_display *ptr; Display(struct wl_display *ptr) : ptr{ptr} { } // 关键:声明友元,允许std::expected访问私有构造 friend std::expected<Display, Error>; public: enum class Error { CannotCreateDisplay, }; Display() = delete; Display(const Display &) = delete; Display(Display &&) = delete; Display &operator=(const Display &) = delete; Display &operator=(Display &&) = delete; ~Display() { wl_display_destroy_clients(this->ptr); wl_display_destroy(this->ptr); } static std::expected<Display, Error> create() { struct wl_display *ptr{wl_display_create()}; if (ptr == nullptr) { return std::unexpected{Error::CannotCreateDisplay}; } // 两种写法都可行: return std::expected<Display, Error>(std::in_place, ptr); // 或者简化为 return {ptr};(视编译器对列表初始化的支持而定) } };
优势:完全不需要移动或拷贝构造,Display对象直接在std::expected的内存空间中就地构造,没有额外开销。
方案2:使用C++23的std::make_expected(需编译器支持)
如果你的项目已经升级到C++23,可以用std::make_expected来简化构造逻辑,同样需要配合友元声明:
// 同样需要在Display类中添加友元声明:friend std::expected<Display, Error>; static std::expected<Display, Error> create() { struct wl_display *ptr{wl_display_create()}; if (ptr == nullptr) { return std::unexpected{Error::CannotCreateDisplay}; } return std::make_expected<Display, Error>(ptr); }
原理:std::make_expected会自动就地构造Display对象,省去手动写std::in_place的麻烦,本质和方案1一致。
为什么原代码报错?
std::expected要构造内部的Display实例,必须调用Display的构造函数。当构造函数私有后,只有Display的成员函数和友元能调用它——std::expected不在此列,因此无法完成构造。而添加移动构造的解决方案,是让create先构造局部Display对象,再通过移动构造把对象转移到std::expected中,但这依赖移动语义,并非最优解。
内容的提问来源于stack exchange,提问作者Ogromny
相关产品推荐
相关产品推荐

