为什么派生类在基类不可移动时可以构造?

Dmi*_*try 57 c++ language-lawyer move-constructor move-semantics c++11

请考虑以下示例:

#include <iostream>
#include <string>
#include <utility>

template <typename Base> struct Foo : public Base {
    using Base::Base;
};

struct Bar {
    Bar(const Bar&) { }
    Bar(Bar&&) = delete;
};

int main() {
    std::cout << std::is_move_constructible<Bar>::value << std::endl; // NO
    std::cout << std::is_move_constructible<Foo<Bar>>::value << std::endl; // YES. Why?!
}
Run Code Online (Sandbox Code Playgroud)

尽管基类是不可移动构造的,为什么编译器会生成移动构造函数?

这是标准还是编译器错误?是否有可能"完美地传播"将构造从基础移动到派生类?

Bri*_*ian 30

因为:

重载解析会忽略定义为已删除的默认移动构造函数.

([class.copy]/11)

Bar的移动构造函数被明确删除,因此Bar无法移动.但由于无法移动成员,因此隐式声明默认情况下隐式删除了Foo<Bar>移动构造函数.因此可以使用其复制构造函数移动.BarFoo<Bar>

编辑:我也忘了提到一个重要的事实,即继承构造函数声明,如using Base::Base不继承默认,复制或移动构造函数,这就是为什么Foo<Bar>没有显式删除的移动构造函数继承自Bar.

  • 嗯,我不明白这一点.原因似乎是"但是Foo <Bar>的移动构造函数在被隐式声明为默认后被隐式删除,因为Bar成员无法移动.因此可以使用其复制构造函数移动Foo <Bar>. " 如你所描述的那样 那么,为什么它不能保持"Bar的移动构造函数被明确删除,因此Bar可以使用其复制构造函数移动"? (2认同)
  • 我认为"因此可以使用其复制构造函数移动`Foo <Bar>`." 措辞非常混乱.以一种不暗示对象被*从*移动的方式进行重写可能会更好,只是(示例)调用`Foo <Bar>(std :: move(other))`是合法的.或者其他的东西. (2认同)
  • @Andreas对于像我这样的凡人,"移动构造"和"移动到"之间似乎没有区别.如果对于一个基类,谓词"A <Base>"不成立,对于派生类来说似乎非常混乱,派生类需要对"Base"对象执行操作,即"A <Base>"断言状态可能性,谓词突然产生"真实".如果这属于这些谓词的"缺点",他们可能会错过在"非直接上下文"中发生的错误,如副实例等,这对我来说似乎是合乎逻辑的.但如果有概念上的原因,我还不明白它. (2认同)

son*_*yao 24

1.的行为 std::is_move_constructible

这是std :: is_move_constructible的预期行为:

没有移动构造函数但带有接受const T&参数的复制构造函数的类型满足std::is_move_constructible.

这意味着使用复制构造函数,仍然可以T从rvalue引用构造T&&.并且Foo<Bar>有一个隐式声明的复制构造函数.

2.隐式声明的移动构造函数 Foo<Bar>

尽管基类是不可移动构造的,为什么编译器会生成移动构造函数?

实际上,移动构造函数Foo<Bar>被定义为已删除,但请注意,重载解析会忽略已删除的隐式声明的移动构造函数.

类的隐式声明或默认移动构造函数T被定义为删除以下任何一种情况:

...
T has direct or virtual base class that cannot be moved (has deleted, inaccessible, or ambiguous move constructors); 
...
Run Code Online (Sandbox Code Playgroud)

重载解析会忽略已删除的隐式声明的移动构造函数(否则会阻止rvalue的复制初始化).

3. Bar和之间的不同行为Foo<Bar>

请注意,移动构造函数Bardeleted显式声明的,移动构造函数Foo<Bar>是隐式声明的并定义为deleted.关键是重载解析会忽略已删除的隐式声明的移动构造函数,这使得可以Foo<Bar>使用其复制构造函数移动构造.但是显式删除的移动构造函数将参与重载解析,意味着当尝试移动构造Bar函数时,将选择已删除的移动构造函数,然后程序格式错误.

这就是为什么Foo<Bar>移动可构建但Bar不是.

该标准对此有明确的陈述.$ 12.8/11复制和移动类对象[class.copy]

超载解析([over.match],[over.over])忽略定义为已删除的默认移动构造函数.[注意:删除的移动构造函数否则会干扰rvalue的初始化,而rvalue可以使用复制构造函数. - 结束说明]

  • @MarcvanLeeuwen:这让人很困惑......像这样的东西是我眼中C++的诅咒. (2认同)