Поскольку ссылки не могут повторно связываться в C ++, любой экземпляр класса со ссылочным членом не может быть переназначен должным образом, если объект, назначаемый из ссылок, не совпадает с объектом, назначенным для него.Но так как вы хотите иметь возможность перемещать Subject
s, вы должны иметь возможность переназначить этих участников.Здесь есть два варианта: вы можете использовать указатели для членов subject_
и target_
или использовать std::reference_wrapper
.
Я лично предпочитаю std::reference_wrapper
, потому что с указателями кто-то незнакомый с кодом может подумать, что это может быть nullptr
, тогда как reference_wrapper
дает понять, что ссылка всегда действительна.Однако, в отличие от указателей, std::reference_wrapper
требует, чтобы ссылочный тип был полным типом до C ++ 20, поэтому одного предварительного объявления недостаточно, и вам необходимо поменять определения State
и Subject
в вас.код для его использования (как вы можете видеть в самом конце этого ответа).
Использование std::reference_wrapper
изменяет ваши State
классы на что-то вроде этого (заметьте, я также добавил недостающее назначение Subject::state_
в конструкторе 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
должен дополнительно обновлять элемент данных target_
при перемещении этого экземпляра Subject
.
Поскольку я предполагаю, что вы не хотите вводить связь, в которой Subject
нужно знать обо всех State
подклассах, которые имеют дополнительные ссылки на Subject
s, как и 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();
}