移动分配运算符和虚拟继承

问题描述

这个社区中已经讨论了类似我的类似问题(有几篇帖子,例如thisthisthisthisthis ),但是最有趣的一个(我想在这里讨论)是this,尽管它并不能真正解决我的问题。我想讨论的是以下警告:

warning: defaulted move assignment for ‘UG’ calls a non-trivial move assignment operator for virtual base ‘G’.

在最后提到的帖子中,one user回答说,此警告是说基类可以移动两次,所以

第二个移动分配来自已移动的对象,其中 可能导致第一招分配的内容为 覆盖。

我知道这是一个问题,最好避免。现在,我有几个从纯虚拟基类继承的类。多继承也涉及到,并在下面的MWE中表示。我想拥有的是在需要时使用move构造函数和move赋值运算符的可能性,这样我就可以做到

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

产生输出(不带注释,我添加了注释以更好地理解输出)

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的执行不会产生任何错误。我还为cout宏添加了__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(),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";
    }
    );
}

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)