如何在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
相关产品推荐
相关产品推荐

