使用反向引用移动多态成员的构造函数

Move constructor for polymorphic members with back reference

本文关键字:动多态 成员 构造函数 移动 引用      更新时间:2023-10-16

我实现了State模式,其中还包括对主题类的引用。

class State {
public:
virtual void doStuff() = 0;
protected:
State(Subject& s) : subject_{s} {}
private:
Subject& subject_;
};
class StateA : public State {
public: 
StateA(Subject& s, Subject& target) : State(s), target_{t} {}
void doStuff() override { /* implementation requiring subject_ */ }  
private:
Subject& target_;
};
class Subject {
public:
void doStuff() { state_->doStuff(); }
State* state_;
};

当我想在需要移动语义的容器中使用主题集合(如std::vector)时,默认的移动构造函数是不够的,因为移动的状态仍然引用旧的主题。在一个例子中,状态甚至需要另一个主题,当移动时会导致无效引用。

如何在此设置中实现正确的移动语义?目前,我在一开始就预留了足够的空间,所以没有必要搬家,但这在未来可能不可行。

由于引用不能在C++中重新绑定,所以具有引用成员的类的任何实例都不能正确地重新分配,除非从中分配的对象引用了与被分配对象相同的对象。但是,如果您想移动Subject,则需要能够重新分配这些成员。这里有两个选项:可以为subject_target_成员使用指针,也可以使用std::reference_wrapper

我个人更喜欢std::reference_wrapper,因为对于指针,不熟悉代码的人可能会认为它可能是nullptr,而rasreference_wrapper明确表示引用总是有效的。然而,与指针相反,std::reference_wrapper要求被引用的类型在C++20之前是一个完整的类型,因此单独的前向声明是不够的,您需要在代码中交换StateSubject的定义才能使用它(正如您在本答案的最后所看到的)。

使用std::reference_wrapperState类更改为类似的类(注意,我还在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_成员,而且当Subject的实例被移动时,StateA类还需要更新target_数据成员。

由于我假设您不想引入耦合,Subject需要了解所有对Subject有额外引用的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_;
};

当然,我们还需要修改StateStateA构造函数来注册正确的回调:

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(); 
}