【发布时间】:2020-10-12 21:21:52
【问题描述】:
类似我的问题已经在这个社区讨论过(有几个帖子,如this、this、this、this 和this),但最有趣的一个(对于什么我想在这里讨论)是this,虽然它并不能真正解决我的问题。我想讨论的是以下警告:
warning: defaulted move assignment for ‘UG’ calls a non-trivial move assignment operator for virtual base ‘G’.
在最后提到的帖子中,one user 回答说这个警告是说基类可以移动两次,所以
第二个移动分配来自一个已经移动的对象,它 可能导致第一次移动分配的内容是 覆盖。
我知道这是一个问题,最好避免。现在,我有几个类继承自纯虚拟基类。还涉及多重继承,并在下面的 MWE 中表示。我想要的是可以在需要时使用移动构造函数和移动赋值运算符,这样我就可以做到
T t3;
T t2 = std::move(t1);
t3 = std::move(t2);
不用担心内存泄漏,并且一切都被正确移动。目前,T t2 = std::move(t1); 工作正常,但t3 = std::move(t2); 不行。我制作了一个 MWE,它很好地代表了我的实际代码,我非常相信 MWE 的解决方案也将成为我的代码的解决方案。 MWE 是:
class G {
public:
G() = default;
G(G&&) = default;
G(const G&) = default;
virtual ~G() = default;
G& operator= (G&& g) {
cout << __PRETTY_FUNCTION__ << endl;
return *this;
}
G& operator= (const G&) = default;
virtual void asdf() = 0; // abstract function to force complexity
string mem_G;
};
class UG : virtual public G {
public:
UG() = default;
UG(UG&& u) = default;
UG(const UG&) = default;
virtual ~UG() = default;
UG& operator= (UG&&) = default;
UG& operator= (const UG&) = default;
void asdf() { mem_G = "asdf"; }
string mem_UG;
};
class T : virtual public G {
public:
T() = default;
T(T&& t) = default;
T(const T&) = default;
virtual ~T() = default;
T& operator= (T&&) = default;
T& operator= (const T&) = default;
virtual void qwer() = 0;
string mem_T;
};
class FT : public UG, virtual public T {
public:
FT() = default;
FT(FT&& f) = default;
FT(const FT&) = default;
virtual ~FT() = default;
FT& operator= (FT&&) = default;
FT& operator= (const FT&) = default;
friend ostream& operator<< (ostream& os, const FT& r) {
os << " mem_G: " << r.mem_G << endl;
os << " mem_UG: " << r.mem_UG << endl;
os << " mem_T: " << r.mem_T << endl;
os << " mem_FT: " << r.mem_FT;
return os;
}
void qwer() { mem_FT = "zxvc"; }
string mem_FT;
};
使用示例中的类,函数
void test() {
FT c1;
c1.mem_G = "I am G";
c1.mem_UG = "I am UG";
c1.mem_T = "I am T";
c1.mem_FT = "I am FT";
cout << "c1" << endl;
cout << c1 << endl;
cout << "Move constructor" << endl;
FT c2 = std::move(c1);
cout << "c1" << endl;
cout << c1 << endl;
cout << "c2" << endl;
cout << c2 << endl;
cout << "Move assignment operator" << endl;
c1 = std::move(c2);
cout << "c1" << endl;
cout << c1 << endl;
cout << "c2" << endl;
cout << c2 << endl;
}
产生输出(没有 cmets,我添加它是为了更好地理解输出)
c1
mem_G: I am G
mem_UG: I am UG
mem_T: I am T
mem_FT: I am FT
Move constructor // correct move of 'c1' into 'c2'
c1
mem_G:
mem_UG:
mem_T:
mem_FT:
c2
mem_G: I am G
mem_UG: I am UG
mem_T: I am T
mem_FT: I am FT
Move assignment operator // moving 'c2' into 'c1' using the move operator will move G's memory twice
G& G::operator=(G&&) // moving once ...
G& G::operator=(G&&) // moving twice ... (not really, because that is not implemented!)
c1
mem_G:
mem_UG: I am UG
mem_T: I am T
mem_FT: I am FT
c2
mem_G: I am G // this memory hasn't been moved because G::operator(G&&)
mem_UG: // does not implement the move.
mem_T:
mem_FT:
请注意 mem_G 在其最后一次出现时如何保持其在 c2 中的值。如果我默认 G& operator=(G&&) 而不是定义它,则结果仅在该行有所不同:
c2
mem_G: // this memory has been moved twice
问题如何在这个继承结构中实现移动赋值运算符(以及移动构造函数,以防需要),以便它们都只移动一次内存?是否可以在没有上述警告的情况下拥有这样的代码?
提前致谢。
编辑感谢this的回答,这个问题已经解决了。我认为人们会发现看到一个完整的解决方案提案很有用,所以我添加了 MWE 的扩展版本,其中包含另外两个类,这样它就有点复杂了。此外,还有main 函数,因此可以测试这些类。最后,我想补充一点,valgrind 在执行代码的调试编译时不会抱怨内存泄漏。
编辑我按照 5 的规则完成了示例,就像评论此答案的一位用户指出的那样,我想我会更新答案。代码编译时没有带有标志-Wall -Wpedantic -Wshadow -Wextra -Wconversion -Wold-style-cast -Wrestrict -Wduplicated-cond -Wnon-virtual-dtor -Woverloaded-virtual 的警告,并且使用valgrind 执行不会产生任何错误。我还添加了 couts 和 __PRETTY_FUNCTION__ 宏,以便任何希望测试代码的人都可以看到函数调用的跟踪。
#include <functional>
#include <iostream>
#include <string>
using namespace std;
class G {
public:
G() {
cout << __PRETTY_FUNCTION__ << endl;
mem_G = "empty";
}
G(const G& g) {
cout << __PRETTY_FUNCTION__ << endl;
copy_full_G(g);
}
G(G&& g) {
cout << __PRETTY_FUNCTION__ << endl;
move_full_G(std::move(static_cast<G&>(g)));
}
virtual ~G() { }
G& operator= (const G& g) {
cout << __PRETTY_FUNCTION__ << endl;
copy_full_G(g);
return *this;
}
G& operator= (G&& g) {
cout << __PRETTY_FUNCTION__ << endl;
move_full_G(std::move(static_cast<G&>(g)));
return *this;
}
friend ostream& operator<< (ostream& os, const G& r) {
os << " mem_G: " << r.mem_G;
return os;
}
virtual void asdf() = 0;
string mem_G;
protected:
void copy_full_G(const G& g) {
cout << __PRETTY_FUNCTION__ << endl;
mem_G = g.mem_G;
}
void move_full_G(G&& g) {
cout << __PRETTY_FUNCTION__ << endl;
mem_G = std::move(g.mem_G);
}
};
class UG : virtual public G {
public:
UG() : G() {
cout << __PRETTY_FUNCTION__ << endl;
mem_UG = "empty";
}
UG(const UG& u) : G() {
cout << __PRETTY_FUNCTION__ << endl;
copy_full_UG(u);
}
UG(UG&& u) {
cout << __PRETTY_FUNCTION__ << endl;
move_full_UG(std::move(static_cast<UG&>(u)));
}
virtual ~UG() { }
UG& operator= (const UG& u) {
cout << __PRETTY_FUNCTION__ << endl;
copy_full_UG(u);
return *this;
}
UG& operator= (UG&& u) {
cout << __PRETTY_FUNCTION__ << endl;
move_full_UG(std::move(static_cast<UG&>(u)));
return *this;
}
friend ostream& operator<< (ostream& os, const UG& r) {
os << " mem_G: " << r.mem_G << endl;
os << " mem_UG: " << r.mem_UG;
return os;
}
void asdf() { mem_G = "asdf"; }
string mem_UG;
protected:
void copy_full_UG(const UG& u) {
cout << __PRETTY_FUNCTION__ << endl;
copy_full_G(u);
mem_UG = u.mem_UG;
}
void move_full_UG(UG&& u) {
cout << __PRETTY_FUNCTION__ << endl;
// move parent class
move_full_G(std::move(static_cast<G&>(u)));
// move this class' members
mem_UG = std::move(u.mem_UG);
}
};
class DG : virtual public G {
public:
DG() : G() {
cout << __PRETTY_FUNCTION__ << endl;
mem_DG = "empty";
}
DG(const DG& u) : G() {
cout << __PRETTY_FUNCTION__ << endl;
copy_full_DG(u);
}
DG(DG&& u) {
cout << __PRETTY_FUNCTION__ << endl;
move_full_DG(std::move(static_cast<DG&>(u)));
}
virtual ~DG() { }
DG& operator= (const DG& u) {
cout << __PRETTY_FUNCTION__ << endl;
copy_full_DG(u);
return *this;
}
DG& operator= (DG&& u) {
cout << __PRETTY_FUNCTION__ << endl;
move_full_DG(std::move(static_cast<DG&>(u)));
return *this;
}
friend ostream& operator<< (ostream& os, const DG& r) {
os << " mem_G: " << r.mem_G << endl;
os << " mem_DG: " << r.mem_DG;
return os;
}
void asdf() { mem_G = "asdf"; }
string mem_DG;
protected:
void copy_full_DG(const DG& u) {
cout << __PRETTY_FUNCTION__ << endl;
copy_full_G(u);
mem_DG = u.mem_DG;
}
void move_full_DG(DG&& u) {
cout << __PRETTY_FUNCTION__ << endl;
// move parent class
move_full_G(std::move(static_cast<G&>(u)));
// move this class' members
mem_DG = std::move(u.mem_DG);
}
};
class T : virtual public G {
public:
T() : G() {
cout << __PRETTY_FUNCTION__ << endl;
mem_T = "empty";
}
T(const T& t) : G() {
cout << __PRETTY_FUNCTION__ << endl;
copy_only_T(t);
}
T(T&& t) {
cout << __PRETTY_FUNCTION__ << endl;
move_only_T(std::move(static_cast<T&>(t)));
}
virtual ~T() { }
T& operator= (const T& t) {
cout << __PRETTY_FUNCTION__ << endl;
copy_only_T(t);
return *this;
}
T& operator= (T&& t) {
cout << __PRETTY_FUNCTION__ << endl;
move_only_T(std::move(static_cast<T&>(t)));
return *this;
}
friend ostream& operator<< (ostream& os, const T& r) {
os << " mem_G: " << r.mem_G << endl;
os << " mem_T: " << r.mem_T;
return os;
}
virtual void qwer() = 0;
string mem_T;
protected:
// Copy *only* T members.
void copy_only_T(const T& t) {
cout << __PRETTY_FUNCTION__ << endl;
mem_T = t.mem_T;
}
// Move *only* T members.
void move_only_T(T&& t) {
cout << __PRETTY_FUNCTION__ << endl;
// if we moved G's members too then we
// would be moving G's members twice!
//move_full_G(std::move(static_cast<G&>(t)));
mem_T = std::move(t.mem_T);
}
};
class FT : public UG, virtual public T {
public:
FT() : T(), UG(){
cout << __PRETTY_FUNCTION__ << endl;
mem_FT = "empty";
}
FT(const FT& f) : G(), T(), UG() {
cout << __PRETTY_FUNCTION__ << endl;
copy_full_FT(f);
}
FT(FT&& f) {
cout << __PRETTY_FUNCTION__ << endl;
move_full_FT(std::move(static_cast<FT&>(f)));
}
virtual ~FT() { }
FT& operator= (const FT& f) {
cout << __PRETTY_FUNCTION__ << endl;
copy_full_FT(f);
return *this;
}
FT& operator= (FT&& other) {
cout << __PRETTY_FUNCTION__ << endl;
// Move-assign FT members
move_full_FT(std::move(static_cast<FT&>(other)));
return *this;
}
friend ostream& operator<< (ostream& os, const FT& r) {
os << " mem_G: " << r.mem_G << endl;
os << " mem_UG: " << r.mem_UG << endl;
os << " mem_T: " << r.mem_T << endl;
os << " mem_FT: " << r.mem_FT;
return os;
}
void qwer() { mem_FT = "zxvc"; }
string mem_FT;
protected:
void copy_full_FT(const FT& f) {
cout << __PRETTY_FUNCTION__ << endl;
copy_full_UG(f);
copy_only_T(f);
mem_FT = f.mem_FT;
}
void move_full_FT(FT&& other) {
cout << __PRETTY_FUNCTION__ << endl;
// Move-assign UG members and also the base class's members
move_full_UG(std::move(static_cast<UG&>(other)));
// Move-assign only T's members
move_only_T(std::move(static_cast<T&>(other)));
// move this class' members
mem_FT = std::move(other.mem_FT);
}
};
class RT : public DG, virtual public T {
public:
RT() : T(), DG() {
cout << __PRETTY_FUNCTION__ << endl;
mem_RT = "empty";
}
RT(const RT& f) : G(), T(), DG() {
cout << __PRETTY_FUNCTION__ << endl;
copy_full_RT(f);
}
RT(RT&& r) {
cout << __PRETTY_FUNCTION__ << endl;
move_full_RT(std::move(static_cast<RT&>(r)));
}
virtual ~RT() { }
RT& operator= (const RT& r) {
cout << __PRETTY_FUNCTION__ << endl;
copy_full_RT(r);
return *this;
}
RT& operator= (RT&& r) {
cout << __PRETTY_FUNCTION__ << endl;
// Move-assign RT members
move_full_RT(std::move(static_cast<RT&>(r)));
return *this;
}
friend ostream& operator<< (ostream& os, const RT& r) {
os << " mem_G: " << r.mem_G << endl;
os << " mem_DG: " << r.mem_DG << endl;
os << " mem_T: " << r.mem_T << endl;
os << " mem_RT: " << r.mem_RT;
return os;
}
void qwer() { mem_RT = "zxvc"; }
string mem_RT;
protected:
void copy_full_RT(const RT& f) {
cout << __PRETTY_FUNCTION__ << endl;
copy_full_DG(f);
copy_only_T(f);
mem_RT = f.mem_RT;
}
void move_full_RT(RT&& other) {
cout << __PRETTY_FUNCTION__ << endl;
// Move-assign DG members and also the base class's members
move_full_DG(std::move(static_cast<DG&>(other)));
// Move-assign only T's members
move_only_T(std::move(static_cast<T&>(other)));
// move this class' members
mem_RT = std::move(other.mem_RT);
}
};
template<class C> void test_move(const function<void (C&)>& init_C) {
C c1;
cout << c1 << endl;
init_C(c1);
cout << "Initialise c1" << endl;
cout << c1 << endl;
cout << "Move constructor: 'c2 <- c1'" << endl;
C c2 = std::move(c1);
cout << "c1" << endl;
cout << c1 << endl;
cout << "c2" << endl;
cout << c2 << endl;
cout << "Move assignment operator: 'c1 <- c2'" << endl;
c1 = std::move(c2);
cout << "c1" << endl;
cout << c1 << endl;
cout << "c2" << endl;
cout << c2 << endl;
}
template<class C> void test_copy(const function<void (C&)>& init_C) {
C c1;
cout << c1 << endl;
cout << "Initialise c1" << endl;
init_C(c1);
cout << c1 << endl;
cout << "Copy constructor: 'c2 <- c1'" << endl;
C c2 = c1;
cout << "c1" << endl;
cout << c1 << endl;
cout << "c2" << endl;
cout << c2 << endl;
cout << "Copy assignment operator: 'c1 <- c2'" << endl;
c1 = c2;
cout << "c1" << endl;
cout << c1 << endl;
cout << "c2" << endl;
cout << c2 << endl;
}
template<class C>
void test(const string& what, const function<void (C&)>& init_C) {
cout << "********" << endl;
cout << "** " << what << " **" << endl;
cout << "********" << endl;
cout << "----------" << endl;
cout << "-- MOVE --" << endl;
cout << "----------" << endl;
test_move<C>(init_C);
cout << "----------" << endl;
cout << "-- COPY --" << endl;
cout << "----------" << endl;
test_copy<C>(init_C);
}
int main() {
test<UG>(
"UG",
[](UG& u) -> void {
u.mem_G = "I am G";
u.mem_UG = "I am UG";
}
);
test<DG>(
"DG",
[](DG& d) -> void {
d.mem_G = "I am G";
d.mem_DG = "I am DG";
}
);
test<FT>(
"FT",
[](FT& u) -> void {
u.mem_G = "I am G";
u.mem_UG = "I am UG";
u.mem_T = "I am T";
u.mem_FT = "I am FT";
}
);
test<RT>(
"RT",
[](RT& u) -> void {
u.mem_G = "I am G";
u.mem_DG = "I am DG";
u.mem_T = "I am T";
u.mem_RT = "I am RT";
}
);
}
【问题讨论】:
-
我坚信五法则绝对适用于您拥有虚拟移动构造函数的情况。最好在这里明确一点。这实际上是警告试图告诉你的内容
-
@SebastianHoffmann 什么是“虚拟移动构造函数”?
-
@aschepler 对不起,我的意思是虚拟移动分配
-
@SebastianHoffmann 这里没有任何虚拟赋值函数,但是,如果有的话,事情会变得更加混乱。
-
好的,所以我们的想法是实现这些运算符。另一个用户发布了一个答案,其中显示了如何在没有内存泄漏的情况下实际做到这一点,所以我会接受它作为帖子的答案。无论如何,谢谢大家。还有@SebastianHoffmann,感谢您提到五法则;我不知道。
标签: c++ inheritance multiple-inheritance move-semantics