c++ - 为什么隐式和显式删除的 move 构造函数会受到不同对待?

标签 c++ c++11 move-semantics

C++11 标准中隐式和显式删除 move 构造函数的不同处理背后的基本原理是什么,关于 move 构造函数的隐式生成包含/继承类?

C++14/C++17 有什么改变吗? (C++14 中的 DR1402 除外)

注意:我明白发生了什么,我明白这是根据 C++11 标准的规则,我对这些暗示这种行为的规则的基本原理感兴趣(请确保不要简单地重申它是这样的,因为标准是这样说的)。


假设一个类ExplicitDelete带有一个显式删除的 move 构造函数和一个显式默认的复制构造函数。这个类不是 move constructible即使有一个兼容的复制构造函数可用,因为重载决策选择了 move 构造函数并且由于它被删除而在编译时失败。

假设一个类ImplicitDelete它包含或继承自 ExplicitDelete什么都不做。由于 C++11 move ctor rules,此类将其 move 构造函数隐式声明为已删除.但是,这个类仍然是 move constructible通过它的复制ctor。 (最后这句话是否与 DR1402 的决议有关?)

然后是一个类Implicit包含/继承自 ImplicitDelete将生成一个非常好的隐式 move 构造函数,调用 ImplicitDelete的拷贝构造器。

那么允许 Implicit 背后的基本原理是什么?能够隐式 move 和ImplicitDelete不能隐式 move ?

在实践中,如果ImplicitImplicitDelete有一些重型可 move 部件(想想vector<string>),我认为没有理由Implicit应该大大优于 ImplicitDelete在 Action 表现上。 ImplicitDelete仍然可以复制 ExplicitDelete从它的隐式 move 构造函数——就像ImplicitImplicitDelete 做.


对我来说,这种行为似乎不一致。如果发生这两件事中的任何一件,我会发现它更一致:

  1. 编译器对隐式和显式删除的 move 构造函数一视同仁:

    • ImplicitDelete变得不move-constructible , 就像 ExplicitDelete
    • ImplicitDelete已删除的 move 构造函数导致 Implicit 中已删除的隐式 move 构造函数(与 ExplicitDeleteImplicitDelete 的处理方式相同)
    • Implicit变得不move-constructible
    • 编译std::move我的代码示例中的行完全失败
  2. 或者,编译器回退为 ExplicitDelete 复制 ctor also :

    • ExplicitDelete的复制构造函数在所有 move 中被调用s,就像 ImplicitDelete
    • ImplicitDelete得到一个合适的隐式 move 构造函数
    • (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 开始的。根据您的定义,此类有一个显式删除的 move 构造函数,但有一个默认的复制构造函数。

有固定类型,既没有复制也没有 move 。有仅 move 类型。还有可复制的类型。

但是可以复制但具有显式删除 move 构造函数的类型?我会说这样的类是矛盾的。

以下是我所看到的三个事实:

  1. 显式删除 move 构造函数应该意味着您不能 move 它。

  2. 显式默认复制构造函数应该意味着您可以复制它(当然是为了本次对话的目的。我知道您仍然可以做一些事情来删除显式默认值)。

  3. 如果一个类型可以被复制,它就可以被 move 。这就是为什么存在关于不参与重载决策的隐式删除 move 构造函数的规则。因此, move 是复制的真子集。

在这种情况下,C++ 的行为是不一致的,因为您的代码是矛盾的。您希望您的类型可复制但不可 move ; C++ 不允许这样做,因此它的行为很奇怪。

看看消除矛盾后会发生什么。如果您在 ExplicitDelete 中显式删除复制构造函数,一切都会再次变得有意义。 ImplicitDelete 的复制/move 构造函数被隐式删除,因此它是不可 move 的。并且 Implicit 的复制/move 构造函数被隐式删除,因此它也是不可 move 的。

如果您编写自相矛盾的代码,C++ 将不会以完全合法的方式运行。

关于c++ - 为什么隐式和显式删除的 move 构造函数会受到不同对待?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33988934/

相关文章:

c++ - 在调试 session 期间获取有关匿名异常的数据

c++ - 删除无效指针是否保证删除正确的大小?

c++ - 将类的 move 成员作为const引用参数传递

c++ - std::vector of movable-only lambdas,这可能吗?

c++ - move 语义和引用语义

c++ - 当新建一个对象并将创建的对象的地址分配给它的基类指针时,编译器会做什么

c++ - "numrt": identifyer not found despite of defining that function

c++ - 将 SIGTERM 绑定(bind)到成员函数

c++ - 如果函数 f() 返回一个指针,这是正确的 : auto* v = f() OR auto v = f()?

c++ - 在 C++ 中使用线程调用具有多个参数的非静态函数