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

时间:2016-08-25 07:23:29

标签: c++ c++11 language-lawyer move-semantics move-constructor

考虑以下示例:

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

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

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

2 个答案:

答案 0 :(得分:30)

由于:

  

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

([class.copy] / 11)

Bar移动构造函数已明确删除,因此无法移动Bar。但由于无法移动Foo<Bar>成员,Bar移动构造函数在被隐式声明为默认后被隐式删除 。因此,Foo<Bar>可以使用其复制构造函数移动。

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

答案 1 :(得分:24)

<强> 1。 std::is_move_constructible

的行为

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

  

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

这意味着使用复制构造函数,仍然可以从右值引用T构造T&&Foo<Bar>有一个Implicitly-declared copy constructor

<强> 2。隐式声明的Foo<Bar>

的移动构造函数
  

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

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

  

T的隐式声明或默认移动构造函数是   定义为以下任何一项中的删除都是真的:

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

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

第3。 BarFoo<Bar>

之间的不同行为

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

这就是Foo<Bar>移动可构建但Bar不可构建的原因。

该标准对此有明确的陈述。 $12.8/11 Copying and moving class objects [class.copy]

  

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