考虑以下示例:
#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?!
}
为什么尽管基类是不可移动构造的,编译器仍会生成移动构造函数?
这是标准中的还是编译器错误?是否有可能“完美地传播”从基类到派生类的移动构造?
因为:
定义为已删除的默认移动构造函数将被重载解析忽略。
([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>&)
是最佳匹配。根据重载解析,继承的构造函数的排名较低。
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])忽略。 [ 注意:删除的移动构造函数会干扰右值的初始化,右值可以使用复制构造函数。 — 尾注]