为什么gcc警告用std :: tuple和虚拟继承调用一个非平凡的移动赋值运算符?

Dev*_*ull 8 c++ gcc clang c++14

在以下示例中,gcc 7发出警告:

"B"的默认移动分配称为虚拟基础'A'的非平凡移动赋值运算符[-Wvirtual-move-assign]

如果我创建一个std::tuple<B>对象.Clang 5没有报告任何问题.如果vector被删除,问题就会消失Base.例子.

#include <tuple>
#include <vector>

class Base
{
public:
    virtual ~Base();
    std::vector<int> v;
};

class A : public Base
{
};

class B : public virtual A
{
};

int main()
{
    B *b = new B;
    B another{*b}; // <<<--- this compiles
    std::tuple<B> t; // <<<--- gives warning
}
Run Code Online (Sandbox Code Playgroud)

为什么会出现std::tuple(并且没有移动分配),如果我需要保持这样的层次结构,那么解决它的正确方法是什么?

Pra*_*ian 9

警告与此无关tuple,它由移动分配触发B.例如,以下代码生成警告

B b;
B t;
t = std::move(b);
Run Code Online (Sandbox Code Playgroud)

GCC文档解释了警告的意图

-Wno-virtual-move-assign
Run Code Online (Sandbox Code Playgroud)

禁止使用非平凡的C++ 11移动赋值运算符从虚拟基础继承警告.这是危险的,因为如果虚拟基地可以沿多个路径到达,则会多次移动,这可能意味着两个对象最终都处于移动状态.如果编写移动赋值运算符以避免从移动的对象移动,则可以禁用此警告.

这是一个演示问题的示例

struct Base
{
    Base() = default;
    Base& operator=(Base&&)
    {
        std::cout << __PRETTY_FUNCTION__ << '\n';
        return *this; 
    }
};

struct A : virtual Base {};

struct B : virtual Base {};

struct C : A, B {};

int main()
{
    C c;
    c = C();
}
Run Code Online (Sandbox Code Playgroud)

这会产生输出

Base& Base::operator=(Base&&)
Base& Base::operator=(Base&&)
Run Code Online (Sandbox Code Playgroud)

显示单个Base实例被移动分配到两次,一次由每个移动赋值运算符AB.第二个移动分配来自已经从对象移动,这可能导致第一个移动分配的内容被覆盖.

请注意,clang也会在我的示例中生成一个警告,它可能检测到A示例中的两个路径无法访问,并且没有发出警告.

修复它的方法是为其实现移动赋值运算符,A并且B可以检测Base已移动并省略第二个移动赋值.