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

时间:2017-10-06 06:03:49

标签: c++ gcc clang c++14

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

  

默认移动任务' B'调用一个非平凡的移动任务   虚拟基地的运营商' A' [-Wvirtual - 移动 - 分配]

如果我创建一个std::tuple<B>对象。 Clang 5没有报告任何问题。如果从vector移除Base,问题就会消失。 Example

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

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

1 个答案:

答案 0 :(得分:7)

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

B b;
B t;
t = std::move(b);

gcc documentation解释了警告的意图

-Wno-virtual-move-assign
     

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

这是一个example来演示问题

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

这会产生输出

Base& Base::operator=(Base&&)
Base& Base::operator=(Base&&)

显示单个Base实例已移动分配两次,每个移动分配操作符AB移动一次。第二个移动分配来自已经从对象移动,这可能导致第一个移动分配的内容被覆盖。

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

解决问题的方法是为AB实施可以检测到Base已移出的移动分配操作符,并省略第二个移动分配。