非可复制非移动类型的创建函数返回类型选型问题
最优方案:修复Pass-Key Idiom配合std::optional的就地构造
你之前尝试Pass-Key没成功,核心原因是没用到std::optional的就地构造(std::in_place)方式,导致需要移动不可移动的B。正确实现后,能在保持B不可复制、不可移动的前提下,安全返回std::optional<B>,同时严格约束B的创建逻辑:
//! do not use outside of the scope where it was created //! ensure sure that A lives longer than B. class B{ A const * const _a; // Pass-Key:仅B自身能创建该钥匙 struct ConstructionKey { friend class B; ConstructionKey() = default; }; // 私有构造函数,必须携带钥匙才能调用 B(A const& a, ConstructionKey) : _a(&a){} static bool checkValidity(const A&){ return true; } public: // 严格禁止复制/移动 B(const B&) = delete; B(B&&) = delete; B& operator=( B&& other ) = delete; B& operator=( const B& other ) = delete; static std::optional<B> create(const A& a){ if(checkValidity(a)){ // 用std::in_place直接在optional内部构造B,避免移动操作 return std::optional<B>(std::in_place, a, ConstructionKey{}); } return std::nullopt; } bool FooBar() const{ bool result = false; // compute stuff based on _a return result; }; };
方案优势
std::in_place直接在std::optional的内部存储区域构造B,不需要移动或复制B,完美适配B不可移动的特性- Pass-Key保证只有
B::create能构造B,外部无法绕过校验直接创建,符合设计初衷 - 保留
std::optional的语义,使用者能直观判断创建是否成功,且B始终在std::optional存储中,无法被转移出当前作用域
备选方案分析
如果不想用Pass-Key,可参考以下方案,但优先级低于上述最优解:
方案1:公开构造函数,要求用户预校验
- 实现最简单,但完全依赖用户自觉调用
checkValidity,容易出现未校验就创建B的情况,风险较高,不推荐
方案2:自定义作用域限定的可选包装类
若不想依赖std::optional,可编写极简自定义包装,完全限制B在当前作用域内:
template<typename T> class ScopeOnlyOptional { alignas(T) std::byte _storage[sizeof(T)]; bool _has_value = false; public: ScopeOnlyOptional() = default; ~ScopeOnlyOptional() { if (_has_value) { std::launder(reinterpret_cast<T*>(_storage))->~T(); } } // 禁止所有复制/移动,确保不会被带出作用域 ScopeOnlyOptional(const ScopeOnlyOptional&) = delete; ScopeOnlyOptional& operator=(const ScopeOnlyOptional&) = delete; ScopeOnlyOptional(ScopeOnlyOptional&&) = delete; ScopeOnlyOptional& operator=(ScopeOnlyOptional&&) = delete; // 仅允许工厂函数创建 template<typename... Args> static ScopeOnlyOptional create(Args&&... args) { ScopeOnlyOptional opt; new (opt._storage) T(std::forward<Args>(args)...); opt._has_value = true; return opt; } // 访问接口 const T* operator->() const { return _has_value ? std::launder(reinterpret_cast<const T*>(_storage)) : nullptr; } const T& operator*() const { return *operator->(); } bool has_value() const { return _has_value; } };
修改B的create函数:
class B { // ... 原有私有成员 ... public: static ScopeOnlyOptional<B> create(const A& a) { if (checkValidity(a)) { return ScopeOnlyOptional<B>::create(a); } return {}; } // ... 原有公开成员 ... };
- 优点:完全限制B在创建作用域内,不会被转移;不依赖
std::optional特性 - 缺点:需要自定义包装类,增加少量代码量
方案3:保留B的可移动性,交由使用者管理
- 可直接使用
std::optional<B>,但允许B被移动会增加其离开创建作用域的风险,违背"避免指针失效"的核心诉求,不推荐
关于std::unique_ptr<B>的疑问
用unique_ptr<B>确实能编译,但它会给使用者传递"拥有对象所有权"的错误暗示,可能导致使用者将B转移到其他作用域,违反B不能离开创建作用域的约束,因此不推荐使用。
内容的提问来源于stack exchange,提问作者Tolar
相关产品推荐
相关产品推荐

