由于引用无法在 C++ 中重新绑定,因此具有引用成员的类的任何实例都无法正确重新分配,除非从引用中分配的对象与分配给的对象相同。但是,当您希望能够移动 Subjects 时,您需要能够重新分配这些成员。这里有两个选项:您可以使用subject_ 和target_ 成员的指针,也可以使用std::reference_wrapper。
我个人更喜欢std::reference_wrapper,因为不熟悉代码的人可能会认为它可能是nullptr,而reference_wrapper 清楚地表明引用始终有效。但是,与指针相比,std::reference_wrapper 要求引用的类型在 C++20 之前是完整类型,因此仅前向声明是不够的,您需要交换 State 和 Subject 的定义代码以便使用它(正如您在此答案的最后看到的那样)。
使用std::reference_wrapper 将您的State 类更改为类似这样(注意,我还在State 的构造函数中添加了Subject::state_ 的缺失赋值):
class State {
public:
State(Subject& s)
: subject_{std::ref(s)} {
s.state_ = this;
}
State(State const&) = delete;
State(State&&) = delete;
State& operator=(State const&) = delete;
State& operator=(State&&) = delete;
virtual ~State() = default;
virtual void doStuff() = 0;
protected:
Subject& subject() {
return subject_;
}
private:
std::reference_wrapper<Subject> subject_;
};
class StateA : public State {
public:
StateA(Subject& s, Subject& target)
: State(s),
target_{std::ref(target)} {
}
void doStuff() override { /* implementation requiring subject() */ }
private:
Subject& target() {
return target_;
}
std::reference_wrapper<Subject> target_;
};
但正如您还指出的那样,当您移动 Subject 时,您需要通知 State 对象 Subject 已移动,以便调整现在悬空的引用 subject_。但是,您不仅需要通知State 类重新分配subject_ 成员,StateA 类还需要在移动Subject 的实例时更新target_ 数据成员。
由于我假设您不想引入一个耦合,其中 Subject 需要知道所有具有对 Subjects 的附加引用的 State 子类,就像 StateA 一样,因此我们需要一个通用的通知机制这样State 的具体(子)类就可以重新分配适当的reference_wrapper 成员。我的想法是让State 注册一个回调,Subject 在移动时调用它。为此,我会使用std::function。这会将 Subject 类更改为如下所示:
class Subject {
public:
Subject() = default;
Subject(Subject const&) = delete;
Subject(Subject&& other)
: state_{std::move(other.state_)},
move_callbacks_{std::move(other.move_callbacks_)} {
for (auto& callback : move_callbacks_) {
callback(this);
}
}
Subject& operator=(Subject const&) = delete;
Subject& operator=(Subject&& other) {
state_ = std::move(other.state_);
move_callbacks_ = std::move(other.move_callbacks_);
for (auto& callback : move_callbacks_) {
callback(this);
}
return *this;
}
~Subject() = default;
void doStuff() { state_->doStuff(); }
State* state_ = nullptr;
std::vector<std::function<void(Subject*)>> move_callbacks_;
};
当然我们还需要修改State和StateA构造函数来注册正确的回调:
State::State(Subject& s)
: subject_{std::ref(s)} {
s.state_ = this;
s.move_callbacks_.emplace_back([this](Subject* new_location) {
subject_ = std::ref(*new_location);
});
}
StateA::StateA(Subject& s, Subject& target)
: State(s),
target_{std::ref(target)} {
target.move_callbacks_.emplace_back([this](Subject* new_location) {
target_ = std::ref(*new_location);
});
}
在重新排序所有内容以便编译后,我们最终得到
#include <cassert>
#include <functional>
class State;
class Subject {
public:
Subject() = default;
Subject(Subject const&) = delete;
Subject(Subject&& other)
: state_{std::move(other.state_)},
move_callbacks_{std::move(other.move_callbacks_)} {
for (auto& callback : move_callbacks_) {
callback(this);
}
}
Subject& operator=(Subject const&) = delete;
Subject& operator=(Subject&& other) {
state_ = std::move(other.state_);
move_callbacks_ = std::move(other.move_callbacks_);
for (auto& callback : move_callbacks_) {
callback(this);
}
return *this;
}
~Subject() = default;
void doStuff();
State* state_ = nullptr;
std::vector<std::function<void(Subject*)>> move_callbacks_;
};
class State {
public:
State(Subject& s)
: subject_{std::ref(s)} {
s.state_ = this;
s.move_callbacks_.emplace_back([this](Subject* new_location) {
subject_ = std::ref(*new_location);
});
}
State(State const&) = delete;
State(State&&) = delete;
State& operator=(State const&) = delete;
State& operator=(State&&) = delete;
virtual ~State() = default;
virtual void doStuff() = 0;
protected:
Subject& subject() {
return subject_;
}
private:
std::reference_wrapper<Subject> subject_;
};
class StateA : public State {
public:
StateA(Subject& s, Subject& target)
: State(s),
target_{std::ref(target)} {
target.move_callbacks_.emplace_back([this](Subject* new_location) {
target_ = std::ref(*new_location);
});
}
void doStuff() override { /* implementation requiring subject() */ }
private:
Subject& target() {
return target_;
}
std::reference_wrapper<Subject> target_;
};
void Subject::doStuff() {
assert(state_ && "Can't call `Subject::doStuff` on a `Subject` that"
"doesn't have an associated state!");
state_->doStuff();
}