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

如何在C++中强制Token对象必须传递至Sink作用域?

问题:强制Token从Source生成后必须传递给Sink

我想要设计一个包含Sink和Source类型的系统,创建由Source生成且必须传递给Sink的Token对象。通过friend声明可强制对象在特定作用域创建,但我不清楚如何强制该对象必须传递至Sink。由于C++不支持破坏性移动,移后对象仍有效并会在自身作用域析构,无法通过链式移动+私有析构实现该约束。现有代码如下:

class Sink;
class Source;

class Token{
  friend class Source;
  friend class Sink;
private:
  Token() = default;
  ~Token() = default;
};

请问能否设计这样的类,让编译器可检查该对象是否已被移动至指定作用域?目前我使用的编译器不支持保证拷贝消除。


解决方案:状态标记+私有析构+移动语义实现约束

核心思路是让Token在移动后进入"已转移"状态,仅允许Sink将Token标记为可析构状态,同时禁止非Sink作用域销毁有效状态的Token。以下分两种实现方案:

方案1:运行时断言检查

这种方案实现简单,通过运行时断言捕获未传递给Sink的Token:

#include <cassert>
#include <utility>

class Sink;
class Source;

class Token {
    friend class Source;
    friend class Sink;

    enum class State { Valid, Moved };
    State state_;

    // 仅Source能创建有效Token
    Token() : state_(State::Valid) {}

    // 私有析构,仅允许已移动的Token析构
    ~Token() {
        assert(state_ == State::Moved && "Error: Token was not passed to Sink before destruction!");
    }

public:
    // 禁止拷贝,确保所有权唯一
    Token(const Token&) = delete;
    Token& operator=(const Token&) = delete;

    // 移动构造:转移所有权,标记原对象为已移动
    Token(Token&& other) noexcept : state_(State::Valid) {
        other.state_ = State::Moved;
    }

    // 移动赋值:同理转移所有权
    Token& operator=(Token&& other) noexcept {
        if (this != &other) {
            state_ = State::Valid;
            other.state_ = State::Moved;
        }
        return *this;
    }

private:
    // 仅Sink可调用,标记Token为已处理(可析构)
    void consume() {
        state_ = State::Moved;
        // 这里添加Sink处理Token的业务逻辑
    }
};

class Source {
public:
    Token generate() {
        return Token{};
    }
};

class Sink {
public:
    void accept(Token token) {
        token.consume();
        // 后续处理逻辑
    }
};

关键逻辑说明:

  • Token的构造仅对Source开放,确保Token只能由Source生成。
  • 移动操作会将原Token标记为Moved状态,原Token析构时不会触发断言。
  • Sink的accept方法必须调用consume,将传入的Token标记为Moved,确保Token在函数结束后析构合法。
  • 若Token未传递给Sink就被销毁,析构时会触发断言,在开发阶段快速发现问题。

方案2:编译期检查(C++20及以上)

利用C++20的requires约束和模板技巧,让编译器直接拒绝未传递给Sink的Token:

#include <utility>
#include <type_traits>

class Sink;
class Source;

class Token {
    friend class Source;
    friend class Sink;

    enum class State { Valid, Moved };
    State state_;

    Token() : state_(State::Valid) {}

    // 仅允许Moved状态的Token析构
    template <State S = State::Moved>
    ~Token() requires (S == State::Moved) {}

    // 禁止Valid状态的Token析构
    template <State S = State::Valid>
    ~Token() requires (S == State::Valid) = delete;

public:
    Token(const Token&) = delete;
    Token& operator=(const Token&) = delete;

    // 移动构造:转移所有权,将原对象转为Moved状态(允许析构)
    Token(Token&& other) noexcept : state_(State::Valid) {
        other.state_ = State::Moved;
        // 通过类型转换触发原对象的合法析构函数
        using MovedToken = std::conditional_t<true, Token, void>;
        static_cast<MovedToken&&>(other);
    }

    // 移动赋值:同理处理
    Token& operator=(Token&& other) noexcept {
        if (this != &other) {
            state_ = State::Valid;
            other.state_ = State::Moved;
            using MovedToken = std::conditional_t<true, Token, void>;
            static_cast<MovedToken&&>(other);
        }
        return *this;
    }

private:
    // 仅Sink可调用,将当前Token转为Moved状态(允许析构)
    void consume() {
        state_ = State::Moved;
        using MovedToken = std::conditional_t<true, Token, void>;
        static_cast<MovedToken&&>(*this);
    }
};

class Source {
public:
    Token generate() {
        return Token{};
    }
};

class Sink {
public:
    void accept(Token token) {
        token.consume();
        // 后续处理逻辑
    }
};

关键逻辑说明:

  • 通过模板析构函数和requires约束,仅Moved状态的Token拥有合法的析构函数。
  • 移动操作和Sink的consume方法会将Token转换为允许析构的状态,确保编译器能找到合法的析构函数。
  • 若Token未传递给Sink就被销毁,编译器会因为无法找到匹配的析构函数而报错,直接在编译阶段阻止非法行为。

内容的提问来源于stack exchange,提问作者Mehno

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 06:37:09