为什么隐式和显式删除的move构造函数被区别对待
Why are implicitly and explicitly deleted move constructors treated differently?
对于包含/继承类的移动构造函数的隐式生成,C++11标准中隐式和显式删除的移动构造函数不同处理背后的原理是什么?
C++14/C++17会改变什么吗?(除了C++14中的DR1402)
注意:我理解正在发生的事情,我理解这是根据C++11标准的规则,我对这些规则暗示这种行为的基本原理感兴趣(请确保不要因为标准这么说而简单地重申它是这样的)。
假设一个类ExplicitDelete
具有一个明确删除的move ctor和一个明确默认的copy ctor。即使有兼容的复制构造函数可用,这个类也不是move constructible
,因为重载解析选择了move构造函数,并且在编译时由于其删除而失败。
假设一个类ImplicitDelete
包含ExplicitDelete
或从CCD_4继承而不做任何其他事情。由于C++11的move ctor规则,该类将隐式声明其move ctors为已删除。但是,这个类通过它的复制ctor仍然是move constructible
。(最后一句话与DR1402的分辨率有关吗?)
然后,包含/继承ImplicitDelete
的类Implicit
将生成一个非常精细的隐式移动构造函数,该构造函数调用ImplicitDelete
的复制构造函数。
那么,允许Implicit
能够隐式移动而ImplicitDelete
不能隐式移动的理由是什么呢?
在实践中,如果Implicit
和ImplicitDelete
有一些重型可移动构件(想想vector<string>
),我认为Implicit
在移动性能上没有理由大大优于ImplicitDelete
。CCD_ 16仍然可以从其隐式移动ctor—就像CCD_ 18对CCD_。
在我看来,这种行为似乎前后矛盾。如果这两件事中的任何一件发生,我会发现它更一致:
-
编译器对隐式和显式删除的移动因子一视同仁:
ImplicitDelete
变成了move-constructible
,就像ExplicitDelete
一样ImplicitDelete
的已删除移动ctor导致Implicit
中的已删除隐式移动ctor(与ExplicitDelete
对ImplicitDelete
所做的相同)Implicit
变为非move-constructible
- 在我的代码示例中,
std::move
行的编译完全失败
-
或者,编译器返回到
ExplicitDelete
:的复制ctorExplicitDelete
的复制构造函数在所有move
中都被调用,就像ImplicitDelete
一样ImplicitDelete
得到一个适当的隐式移动因子- (
Implicit
在此场景中保持不变) - 代码样本的输出指示
Explicit
成员总是被移动
下面是一个完整的工作示例:
#include <utility>
#include <iostream>
using namespace std;
struct Explicit {
// prints whether the containing class's move or copy constructor was called
// in practice this would be the expensive vector<string>
string owner;
Explicit(string owner) : owner(owner) {};
Explicit(const Explicit& o) { cout << o.owner << " is actually copyingn"; }
Explicit(Explicit&& o) noexcept { cout << o.owner << " is movingn"; }
};
struct ExplicitDelete {
ExplicitDelete() = default;
ExplicitDelete(const ExplicitDelete&) = default;
ExplicitDelete(ExplicitDelete&&) noexcept = delete;
};
struct ImplicitDelete : ExplicitDelete {
Explicit exp{"ImplicitDelete"};
};
struct Implicit : ImplicitDelete {
Explicit exp{"Implicit"};
};
int main() {
ImplicitDelete id1;
ImplicitDelete id2(move(id1)); // expect copy call
Implicit i1;
Implicit i2(move(i1)); // expect 1x ImplicitDelete's copy and 1x Implicit's move
return 0;
}
那么,允许隐式移动和隐式删除不能隐式移动背后的理由是什么呢?
理由是:你所描述的案例没有意义。
看,这一切都是因为ExplicitDelete
而开始的。根据您的定义,这个类有一个显式删除的移动构造函数,但有一个默认的复制构造函数。
有一些固定类型,既没有复制也没有移动。有只移动的类型。还有一些可复制的类型。
但是一个可以复制但有一个显式删除移动构造函数的类型?我想说,这样的阶级是矛盾的。
在我看来,以下是三个事实:
-
明确删除移动构造函数意味着你不能移动它
-
显式默认复制构造函数应该意味着你可以复制它(当然,为了这个对话的目的。我知道你仍然可以做一些事情来删除显式默认构造函数)。
-
如果一个类型可以被复制,那么它就可以被移动。这就是为什么存在关于隐式删除的移动构造函数不参与重载解析的规则。因此,移动是复制的一个子集。
C++在此实例中的行为是不一致的,因为您的代码是矛盾的。你希望你的字体是可复制的,但不能移动;C++不允许这样做,所以它的行为很奇怪。
看看当你消除矛盾时会发生什么。如果显式删除ExplicitDelete
中的复制构造函数,那么一切都有意义了。ImplicitDelete
的复制/移动构造函数被隐式删除,因此它是不可移动的。Implicit
的复制/移动构造函数被隐式删除,因此它也是不可移动的。
如果您编写矛盾的代码,C++将不会以完全合法的方式运行。
- 使用仅使用一次的变量调用的复制构造函数.这可能是通过调用move构造函数进行编译器优化的情况吗
- 添加自定义析构函数时,Move 构造函数在派生类中消失
- std::映射,只有move构造函数可用
- 为什么我的代码中没有调用move构造函数
- std::转换move构造函数的模板专业化的变体
- 如何调用move构造函数
- 我可以将 std::move 与不提供 move 构造函数的类一起使用吗?
- 为什么noexcept move构造函数在向量重新分配期间没有被调用
- 为什么在声明析构函数时必须声明 copy & move 构造函数?
- 如何在我自己的类 Vector 中使用 Move 构造函数而不是 Move 赋值运算符
- GoogleTest Move 构造函数覆盖率
- C - 构造函数,复制构造函数,MOVE构造函数,驱动器
- 如何在基类中调用 "move" '&&' 构造函数?在C++
- 如果我有一个向量(或类似的东西)成员变量,那么move构造函数看起来怎么样
- std::vector的move构造函数是否调用项的move构造器
- 为什么我们需要在move构造函数中将右值引用设置为null
- move构造函数在C++中调用了两次吗
- 实现move构造函数如何影响返回值优化
- 不能默认使用Move构造函数
- C++合成的move构造函数如何受到volatile和虚拟成员的影响