在基类中删除移动构造函数并不会阻止派生类对象从函数中返回。

19
给定基类 A 和派生类 B,A 有一个已删除的移动构造函数。
class A {
public: 
  A()  {}
  A(const A&) = default;
  A(A&&) = delete; 
};

class B : public A
{ 
};

在这种情况下,由于已删除的移动构造函数,以下函数无法编译:
A f() {
  A a;
  return a;
}

但是对于B的类似功能却没有报告任何错误。
B g() {
  B b;
  return b;
}

这是否意味着B中的移动构造函数没有被删除?我想知道标准中的规定是什么。

3
在我看来,根据C++17的此段落f的情况也应该编译通过。相关部分:“如果第一个重载解析失败或未执行,或者所选构造函数的第一个参数的类型不是指向对象类型的右值引用(可能带有cv限定符),则将重新执行重载解析,将对象视为左值。” GCC对我有效,但Clang无效:https://godbolt.org/z/8EbcnfTfb。 - Daniel Langr
1
但也许我错了。这段话似乎也很相关,它说程序实际上是不完整的(就我所理解的):https://timsong-cpp.github.io/cppwp/n4659/dcl.fct.def.delete#2。 - Daniel Langr
3
@DanielLangr 是的,https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93106 - cigien
1
一个可复制但不可移动的类几乎总是一个错误,我并不惊讶它们在这里表现不良。 - HolyBlackCat
2个回答

23
B中的移动构造函数被删除,但不参与重载决议。根据cppreference
对于类T,如果以下任何一项为真,则隐式声明或默认的移动构造函数将被定义为已删除
  • T具有无法移动的非静态数据成员(具有已删除、不可访问或模糊的移动构造函数);
  • T具有无法移动的直接或虚拟基类(具有已删除、不可访问或模糊的移动构造函数);
  • T具有具有已删除或不可访问析构函数的直接或虚拟基类或非静态数据成员;
  • T是类似联合体的类,并具有具有非平凡移动构造函数的变量成员。
被删除的默认移动构造函数不会参与重载决议(否则它会阻止从右值进行复制初始化)。
第二个子弹点适用: B 具有一个带有删除移动构造函数的直接基类 A。因此,B 的隐式声明移动构造函数被定义为已删除。
然而,在返回语句评估要使用的B构造函数时,已删除的移动构造函数不被考虑,但有效的复制构造函数会被使用。

1
谢谢你的回答!那么为什么函数A f()不能编译呢?为什么它不能使用类A的复制构造函数? - Getter

13

B 的移动构造函数被删除了,正如 @Nathan Pierson 已经回答的那样

你可以从 g 返回局部变量 b 的原因是,如上所述,B隐式删除移动构造函数没有参与重载决议,因此编译器选择了 B 的默认复制构造函数。

为了证明上述内容,请看一下 以下代码,在其中添加一个只能移动的成员到 B 中:

class B : public A { 
  std::unique_ptr<int> ptr;
public: 
  B() {}
  // needs this to compile:
  //   B(B&& b): A(b), ptr(std::move(b.ptr)) {}  
};

B g() {
  B b;
  // now this would fail
  // B doesn't have a default move ctor
  // (it is implicitly deleted because of A)
  // and the default copy ctor is not valid
  return b; 
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接