为什么派生类可以移动构造,而基类则不能?

问题描述 投票:0回答:2

考虑以下示例:

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

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

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

c++ c++11 language-lawyer move-semantics move-constructor
2个回答
31
投票

因为:

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

([class.copy]/11)

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

编辑:请注意,

Foo<Bar>
继承了构造函数集
Bar::Bar
,包括其复制和移动构造函数
Bar(const Bar&)
Bar(Bar&&)
。但是
Foo<Bar>
也有自己的隐式声明的复制和移动构造函数,
Foo<Bar>(const Foo<Bar>&)
Foo<Bar>(Foo<Bar>&&)
,由于上述原因,后者被重载解析忽略。当尝试移动构造
Foo<Bar>
对象时,隐式声明的复制构造函数
Foo<Bar>(const Foo<Bar>&)
是最佳匹配。根据重载解析,继承的构造函数的排名较低。


27
投票

1。

std::is_move_constructible

的行为

这是 std::is_move_constructible:

的预期行为

没有移动构造函数但具有接受

const T&
参数的复制构造函数的类型,满足
std::is_move_constructible

这意味着使用复制构造函数仍然可以从右值引用

T
构造
T&&
。并且
Foo<Bar>
有一个 隐式声明的复制构造函数

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); 
...

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

3.

Bar
Foo<Bar>

之间的不同行为

请注意,

Bar
的移动构造函数显式声明为
deleted
,而
Foo<Bar>
的移动构造函数则隐式声明并定义为
deleted
。要点是 删除的隐式声明的移动构造函数会被重载决议忽略 ,这使得可以使用其复制构造函数移动构造
Foo<Bar>
。但是显式删除的移动构造函数将参与重载决策,意味着当尝试移动构造函数时
Bar
删除的移动构造函数将被选择,那么程序是错误的。

这就是为什么

Foo<Bar>
是可移动构造的,而
Bar
则不是。

标准对此有明确的说明。 $12.8/11 复制和移动类对象 [课程.副本]

定义为已删除的默认移动构造函数会被重载解析([over.match]、[over.over])忽略。 [ 注意:删除的移动构造函数会干扰右值的初始化,右值可以使用复制构造函数。 — 尾注]

© www.soinside.com 2019 - 2024. All rights reserved.