移动具有反向引用的多态成员的构造函数
Move constructor for polymorphic members with back reference
我实现了状态模式,其中还包括对主题 class 的引用。
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
)时,默认的移动构造函数是不够的,因为移动状态仍然引用旧主题。在一个实例中,该州甚至需要另一个主题,当移动该主题时会导致无效引用。
如何在此设置中实现正确的移动语义?目前,我一开始就预留了足够的space,所以不需要搬家,但以后可能不可行。
由于引用无法在 C++ 中重新绑定,任何具有引用成员的 class 实例都无法正确重新分配,除非从引用分配的对象与分配给的对象相同。但是,由于您希望能够移动 Subject
,因此您需要能够重新分配这些成员。这里有两个选项:您可以使用 subject_
和 target_
成员的指针,也可以使用 std::reference_wrapper
。
我个人更喜欢 std::reference_wrapper
,因为对于指针,不熟悉代码的人可能会认为它可能是 nullptr
,而 reference_wrapper
清楚地表明引用始终有效。但是,与指针相反,std::reference_wrapper
要求引用类型是 C++20 之前的完整类型,因此仅靠前向声明是不够的,您需要交换 State
和 Subject
在你的代码中以便使用它(正如你在这个答案的最后看到的那样)。
使用 std::reference_wrapper
将您的 State
classes 更改为类似这样的东西(注意我还在 [=22= 的构造函数中添加了缺少的 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
class重新分配subject_
成员,StateA
class还需要更新target_
] 数据成员,当 Subject
的实例被移动时。
因为我假设您不想引入耦合,其中 Subject
需要了解所有 State
subclass 有额外引用 Subject
就像 StateA
因此我们需要一个通用的通知机制,以便 State
的具体(子)class 可以重新分配适当的 reference_wrapper
成员。我的想法是让 State
注册一个 Subject
在移动时调用的回调。为此,我会使用 std::function
。这会将 Subject
class 更改为如下内容:
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();
}
我实现了状态模式,其中还包括对主题 class 的引用。
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
)时,默认的移动构造函数是不够的,因为移动状态仍然引用旧主题。在一个实例中,该州甚至需要另一个主题,当移动该主题时会导致无效引用。
如何在此设置中实现正确的移动语义?目前,我一开始就预留了足够的space,所以不需要搬家,但以后可能不可行。
由于引用无法在 C++ 中重新绑定,任何具有引用成员的 class 实例都无法正确重新分配,除非从引用分配的对象与分配给的对象相同。但是,由于您希望能够移动 Subject
,因此您需要能够重新分配这些成员。这里有两个选项:您可以使用 subject_
和 target_
成员的指针,也可以使用 std::reference_wrapper
。
我个人更喜欢 std::reference_wrapper
,因为对于指针,不熟悉代码的人可能会认为它可能是 nullptr
,而 reference_wrapper
清楚地表明引用始终有效。但是,与指针相反,std::reference_wrapper
要求引用类型是 C++20 之前的完整类型,因此仅靠前向声明是不够的,您需要交换 State
和 Subject
在你的代码中以便使用它(正如你在这个答案的最后看到的那样)。
使用 std::reference_wrapper
将您的 State
classes 更改为类似这样的东西(注意我还在 [=22= 的构造函数中添加了缺少的 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
class重新分配subject_
成员,StateA
class还需要更新target_
] 数据成员,当 Subject
的实例被移动时。
因为我假设您不想引入耦合,其中 Subject
需要了解所有 State
subclass 有额外引用 Subject
就像 StateA
因此我们需要一个通用的通知机制,以便 State
的具体(子)class 可以重新分配适当的 reference_wrapper
成员。我的想法是让 State
注册一个 Subject
在移动时调用的回调。为此,我会使用 std::function
。这会将 Subject
class 更改为如下内容:
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();
}