该警告对我来说似乎是不言自明的,它告诉您移动分配派生类型将导致移动分配基数两次。
减少它很简单,只需使用虚拟基和两个路径来创建继承层次结构即可:
#include <stdio.h>
struct V {
V& operator=(V&&) { puts("moved"); return *this; }
};
struct A : virtual V { };
struct B : virtual V { };
struct C : A, B { };
int main() {
C c;
c = C{};
}
这将打印"moved"
两次,因为每个的隐式移动赋值运算符A
, B
and C
将进行成员分配,这意味着两者A::operator=(A&&)
and B::operator=(B&&)
将分配基类。正如艾伦所说,这是该标准的有效实施。 (该标准指定在构造时只有最派生的类型才会构造虚拟基,但它对赋值没有相同的要求)。
这不是特定于移动分配,将基类更改为仅支持复制分配而不支持移动分配将打印"copied"
twice:
struct V {
V& operator=(const V&) { puts("copied"); return *this; }
};
发生这种情况的原因完全相同A::operator=(A&&)
and B::operator=(B&&)
将分配基类。编译器不会针对这种情况发出警告,因为执行两次复制分配(可能)只是次优,而不是错误。对于移动分配,它可能会丢失数据。
如果您的虚拟基础实际上没有任何需要复制或移动的数据,或者仅具有可轻松复制的数据成员,则使其仅支持复制而不移动将抑制警告:
struct V {
V& operator=(const V&) = default;
};
这个复制赋值运算符仍然会被调用两次,但由于它不执行任何操作,所以没有问题。什么事都不做两次,还是什么事都没有。
(GCC 在这里似乎比 Clang 聪明一点,它不会警告虚拟基地的移动赋值运算符在微不足道的情况下被调用两次,因为微不足道的移动相当于复制,因此不太可能成为问题)。
如果虚拟基础确实有需要在分配时复制的数据,那么使其进行复制而不是移动可能仍然是一个不错的选择,但这取决于类型和用途。您可能需要在层次结构的每个级别显式定义复制和移动分配。虚拟基地很棘手并且很难正确使用,尤其是在面对复制或移动时。将具有虚拟基的类型视为可以轻松复制和移动的值类型可能是一个设计错误。
iostreams 层次结构使用虚拟基,但做得很仔细且正确。 iostream 类型是不可复制的,只能移动,派生类型显式定义移动赋值以确保basic_ios<>
基类仅更新一次。具体来说,basic_iostream::operator=(basic_iostream&&)
仅运行于basic_istream
基础,而不是basic_ostream
一。上面的例子的等价物是:
struct C : A, B {
C& operator=(C&& c) {
static_cast<A&>(*this) = static_cast<A&&>(c);
return *this;
}
};
Iostream 根本不可复制,直到 C++11,此时右值引用和移动语义使得使用有用的语义成为可能。如果您的类在 C++03 中始终是可复制的,那么它可能已经是一个有问题的设计,本应是不可复制的,或者仔细编写了复制操作而不是隐式定义的操作。
简而言之,任何时候你有虚拟基地,你都需要非常仔细地考虑构建、分配和销毁如何工作,以及复制和分配对于该类型是否有意义。