使用反向引用移动多态成员的构造函数
Move constructor for polymorphic members with back reference
我实现了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之前是一个完整的类型,因此单独的前向声明是不够的,您需要在代码中交换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_
成员,而且当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_;
};
当然,我们还需要修改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();
}
- 找不到成员对象:没有名为get_event()的成员,也处理多态性和向量
- 访问存储在向量C++中的结构的多态成员
- 运行时多态性 - 箭头运算符访问了错误的成员?
- 非虚拟基的多态成员类
- C++多态性:有没有办法找到对象成员函数的地址?
- 如果基类指针无法访问派生类成员函数,那么多态性有什么方便的呢?
- 无法从多态嵌套类访问包含类的成员
- C 多态性:允许模棱两可的成员类型
- 没有新成员的模板多态性派生类的大小
- 向上转换指向数据成员及其多态行为的指针
- 多态性:成员访问和吸气器给出不同的结果
- 如何在保持多态性的同时,将成员函数添加到需要它的继承类中,而不会影响其他同级类?
- 多态unique_ptr类成员
- 成员变量多态性
- 具有引用成员变量的多态性
- 使用反向引用移动多态成员的构造函数
- 将异步与多态成员函数一起使用
- 多态成员变量
- 纠结于多态成员的设计决策
- C++:多态成员会保持不变吗