为什么隐式和显式删除的移动构造函数会受到不同对待?
Why are implicitly and explicitly deleted move constructors treated differently?
C++11标准中隐式和显式删除移动构造函数的不同处理背后的基本原理是什么,尊重containing/inheriting classes?
的移动构造函数的隐式生成
C++14/C++17 有什么改变吗? (C++14 中的 DR1402 除外)
注意:我明白发生了什么,我明白这是根据 C++11 标准的规则,我对这些暗示这种行为的规则的基本原理感兴趣(请确保不要简单地重申它就是这样,因为标准是这样说的)。
假设一个 class ExplicitDelete
有一个显式删除的移动构造函数和一个显式默认的复制构造函数。这个 class 不是 move constructible
即使 compatible copy ctor 可用,因为重载解析选择移动构造函数并且由于它的删除而在编译时失败。
假设一个 class ImplicitDelete
包含或继承自 ExplicitDelete
并且什么都不做。由于 C++11 移动构造函数规则,此 class 将其移动构造函数隐式声明为已删除。然而,这个 class 仍然是 move constructible
通过它的复制构造函数。 (最后这句话与 DR1402 的分辨率有关吗?)
然后 class Implicit
containing/inheriting 来自 ImplicitDelete
将生成一个完美的隐式移动构造函数,它调用 ImplicitDelete
的复制构造函数。
那么允许 Implicit
能够隐式移动而 ImplicitDelete
不能隐式移动背后的基本原理是什么?
在实践中,如果 Implicit
和 ImplicitDelete
有一些重型可移动部件(想想 vector<string>
),我认为 Implicit
没有理由要优越得多ImplicitDelete
的移动性能。 ImplicitDelete
仍然可以从其隐式移动构造函数复制 ExplicitDelete
——就像 Implicit
对 ImplicitDelete
.
所做的那样
对我来说,这种行为似乎不一致。如果发生这两件事中的任何一件,我会发现它更一致:
编译器对隐式和显式删除的移动构造函数一视同仁:
ImplicitDelete
变得不是 move-constructible
,就像 ExplicitDelete
ImplicitDelete
的已删除移动构造函数导致 Implicit
中已删除的隐式移动构造函数(与 ExplicitDelete
对 ImplicitDelete
的相同方式)
Implicit
变成不move-constructible
- 在我的代码示例中编译
std::move
行完全失败
或者,编译器返回复制 ctor also for ExplicitDelete
:
ExplicitDelete
的复制构造函数在所有 move
中被调用,就像 ImplicitDelete
ImplicitDelete
得到一个正确的隐式移动 ctor
- (
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 copying\n"; }
Explicit(Explicit&& o) noexcept { cout << o.owner << " is moving\n"; }
};
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;
}
So what is the rationale behind allowing Implicit to be able to move implicitly and ImplicitDelete not to be able to move implicitly?
理由是这样的:你描述的情况没有意义。
看,这一切都是因为ExplicitDelete
才开始的。根据您的定义,此 class 有一个显式删除的移动构造函数,但有一个默认的复制构造函数。
有固定类型,既没有复制也没有移动。有仅移动类型。还有可复制的类型。
但是一个可以复制但有一个显式删除移动构造函数的类型?我会说这样的class是矛盾的
以下是我所看到的三个事实:
明确删除移动构造函数应该意味着您不能移动它。
显式默认复制构造函数应该意味着您可以复制它(当然是为了本次对话的目的。我知道您仍然可以做一些事情来删除显式默认值)。
类型可以复制,就可以移动。这就是为什么存在关于不参与重载决策的隐式删除移动构造函数的规则。因此,移动是复制的真子集。
C++ 在此实例中的行为是不一致的,因为您的代码是矛盾的。您希望您的类型可复制但不可移动; C++ 不允许这样做,所以它的行为很奇怪。
看看当你消除矛盾时会发生什么。如果你显式地删除 ExplicitDelete
中的复制构造函数,一切又变得有意义了。 ImplicitDelete
的 copy/move 构造函数被隐式删除,因此它是固定的。 Implicit
的 copy/move 构造函数被隐式删除,因此它也是固定的。
如果您编写自相矛盾的代码,C++ 将不会以完全合法的方式运行。
C++11标准中隐式和显式删除移动构造函数的不同处理背后的基本原理是什么,尊重containing/inheriting classes?
的移动构造函数的隐式生成C++14/C++17 有什么改变吗? (C++14 中的 DR1402 除外)
注意:我明白发生了什么,我明白这是根据 C++11 标准的规则,我对这些暗示这种行为的规则的基本原理感兴趣(请确保不要简单地重申它就是这样,因为标准是这样说的)。
假设一个 class ExplicitDelete
有一个显式删除的移动构造函数和一个显式默认的复制构造函数。这个 class 不是 move constructible
即使 compatible copy ctor 可用,因为重载解析选择移动构造函数并且由于它的删除而在编译时失败。
假设一个 class ImplicitDelete
包含或继承自 ExplicitDelete
并且什么都不做。由于 C++11 移动构造函数规则,此 class 将其移动构造函数隐式声明为已删除。然而,这个 class 仍然是 move constructible
通过它的复制构造函数。 (最后这句话与 DR1402 的分辨率有关吗?)
然后 class Implicit
containing/inheriting 来自 ImplicitDelete
将生成一个完美的隐式移动构造函数,它调用 ImplicitDelete
的复制构造函数。
那么允许 Implicit
能够隐式移动而 ImplicitDelete
不能隐式移动背后的基本原理是什么?
在实践中,如果 Implicit
和 ImplicitDelete
有一些重型可移动部件(想想 vector<string>
),我认为 Implicit
没有理由要优越得多ImplicitDelete
的移动性能。 ImplicitDelete
仍然可以从其隐式移动构造函数复制 ExplicitDelete
——就像 Implicit
对 ImplicitDelete
.
对我来说,这种行为似乎不一致。如果发生这两件事中的任何一件,我会发现它更一致:
编译器对隐式和显式删除的移动构造函数一视同仁:
ImplicitDelete
变得不是move-constructible
,就像ExplicitDelete
ImplicitDelete
的已删除移动构造函数导致Implicit
中已删除的隐式移动构造函数(与ExplicitDelete
对ImplicitDelete
的相同方式)Implicit
变成不move-constructible
- 在我的代码示例中编译
std::move
行完全失败
或者,编译器返回复制 ctor also for
ExplicitDelete
:ExplicitDelete
的复制构造函数在所有move
中被调用,就像ImplicitDelete
ImplicitDelete
得到一个正确的隐式移动 ctor- (
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 copying\n"; }
Explicit(Explicit&& o) noexcept { cout << o.owner << " is moving\n"; }
};
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;
}
So what is the rationale behind allowing Implicit to be able to move implicitly and ImplicitDelete not to be able to move implicitly?
理由是这样的:你描述的情况没有意义。
看,这一切都是因为ExplicitDelete
才开始的。根据您的定义,此 class 有一个显式删除的移动构造函数,但有一个默认的复制构造函数。
有固定类型,既没有复制也没有移动。有仅移动类型。还有可复制的类型。
但是一个可以复制但有一个显式删除移动构造函数的类型?我会说这样的class是矛盾的
以下是我所看到的三个事实:
明确删除移动构造函数应该意味着您不能移动它。
显式默认复制构造函数应该意味着您可以复制它(当然是为了本次对话的目的。我知道您仍然可以做一些事情来删除显式默认值)。
类型可以复制,就可以移动。这就是为什么存在关于不参与重载决策的隐式删除移动构造函数的规则。因此,移动是复制的真子集。
C++ 在此实例中的行为是不一致的,因为您的代码是矛盾的。您希望您的类型可复制但不可移动; C++ 不允许这样做,所以它的行为很奇怪。
看看当你消除矛盾时会发生什么。如果你显式地删除 ExplicitDelete
中的复制构造函数,一切又变得有意义了。 ImplicitDelete
的 copy/move 构造函数被隐式删除,因此它是固定的。 Implicit
的 copy/move 构造函数被隐式删除,因此它也是固定的。
如果您编写自相矛盾的代码,C++ 将不会以完全合法的方式运行。