为什么在派生类的移动赋值运算符执行时,C++不保证虚基类子对象只会被移动一次?

编程语言 2026-07-07

我刚刚收到了我的第一个:

warning: defaulted move assignment calls a non-trivial move assignment operator
for virtual base [-Wvirtual-move-assign]

来自 g++ 的警告,我因此有点恼火,因为我相信这并不是一个问题。

假设我的类结构是:

继承自 移动赋值运算符
A 自定义
B virtual A = default 在类定义中
C virtual A = default 在类定义中
D virtual B, virtual C = default 在类定义中

为什么我应该因为移动赋值而收到警告,而不是语言要求编译器确保,在对 D 的移动中,A 实例只被移动一次?

这似乎在担心,BC 的移动赋值假设它们可以安全地从 A 实例进行移动,那么谁又能说它们不会在某处都被使用;但反驳是,这种情况只能发生在那些编译并从 BC 同时继承的类中——也就是说只有在编译器能够注意到 BC 的移动赋值被默认化时才会发生,因此避免天真地同时调用它们两者。事实上我会认为,只有对于 BC 的自定义移动赋值才应当发出警告,而编译器无法设计让 A 的移动仅被移动一次。或者——我是不是理解错了?

解决方案

注:这个回答以前的版本是错误的(现在也可能仍然如此)。为了参考,下面保留旧版本。由于标准仍然允许(但不要求)多次赋值发生,因此旧版本在确定在发生多次赋值时的情况及原因方面可能仍然有用。

Note: this answer used to be incorrect (it might still be). The old version is preserved below for reference. Since the standard still allows (but does not require) that multiple assignment occurs, the old version might still be useful in determining what happens and why if multiple assignment occurs.

旧的错误版本

这是因果与结果的链条。

  1. gcc警告你,因为你编写的代码在众所周知的情况下不能按你期望工作(也就是对虚拟基类的移动恰好一次)。
  2. 这段代码之所以不能按你预期工作,是因为要实现这一点需要对语言做出修改,而谁也不愿意做这样的修改。
  3. 没有谁愿意做这些修改,因为需要它们的人很少,成本对收益的比率要么未知,要么很差。

那些修改是什么?

一种可能的语言变动方式是让 D 的默认移动赋值在你期望的情况下工作若且仅若 BC 的移动赋值要么被隐式默认,要么被显式默认,以便 =defaultD 的移动构造函数中可见。这可以被认为是(a) 过于复杂,因为它是语言中的一个几乎从未使用的特例;(b) 脆弱,因为在错误的位置加入 =default,或让移动赋值变为非默认态,会悄无声息地破坏代码。

另一种可能的修改是,除非能够让它按你期望的方式工作,否则禁止 D 的默认化移动赋值。这个修改非常简单且一致,但会禁止有用且有效的用例。对于一个对象如果它确实不管理任何资源,那么多次从该对象“移动”并没有错。

如果你希望在更广的情境下让 D 的移动构造函数按你的期望工作,这将需要更深层次的变革,极有可能破坏现有代码。或者至少我无法想象它到底怎么实现。如果你真的能做到,我鼓励你提交一个提案。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章