You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

非可复制非移动类型的创建函数返回类型选型问题

最优方案:修复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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 13:25:19