为什么隐式和显式删除移动构造函数的处理方式不同?

Irf*_*rfy 11 c++ move-semantics c++11

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

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

注意:我理解发生了什么,我理解它是根据C++ 11标准的规则,我对这些规则的理由感兴趣,这意味着这种行为(请确保不要简单地重申它是这样的方式这是因为标准说的是这样).


假设一个类ExplicitDelete具有明确删除的移动ctor和明确默认的复制ctor.move constructible即使兼容的拷贝ctor可用,这个类也是不可用的,因为重载决策选择了移动构造函数,并且由于删除而在编译时失败.

假设一个ImplicitDelete包含或继承的类,ExplicitDelete不执行任何其他操作.由于C++ 11移动ctor规则,此类将其ctor隐式声明为已删除.但是,这个课程仍将move constructible通过其复制ctor.(这最后的陈述是否与DR1402的解决方案有关?)

然后,Implicit包含/继承的类ImplicitDelete将生成一个完全精细的隐式移动构造函数,该构造函数调用ImplicitDelete复制ctor.

那么允许Implicit能够隐式移动而ImplicitDelete不能隐式移动的理由是什么呢?

在实践中,如果Implicit并且ImplicitDelete有一些重型的可移动成员(想想vector<string>),我认为没有理由ImplicitImplicitDelete移动性能更优越.ImplicitDelete仍然可以复制ExplicitDelete其隐含的动作ctor-就像Implicit它一样ImplicitDelete.


对我来说,这种行为似乎不一致.如果发生以下两种情况之一,我会发现它更加一致:

  1. 编译器同时处理隐式和显式删除的移动ctors:

    • ImplicitDelete变得不move-constructible,就像ExplicitDelete
    • ImplicitDelete的删除的举动男星导致删除的隐含移动构造函数Implicit(在以同样的方式ExplicitDelete做,为ImplicitDelete)
    • Implicit 变得没有 move-constructible
    • std::move在我的代码示例中,该行的编译完全失败
  2. 或者,编译器将回退到拷贝构造函数用于ExplicitDelete:

    • ExplicitDelete所有moves 中都调用了复制构造函数,就像是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;
}
Run Code Online (Sandbox Code Playgroud)

Nic*_*las 5

那么允许Implicit能够隐式移动而ImplicitDelete无法隐式移动的理由是什么呢?

理由是:你描述的情况没有意义.

看,所有这一切都是因为ExplicitDelete.根据您的定义,此类具有显式删除的移动构造函数,但是具有默认的复制构造函数.

有固定类型,既没有复制也没有移动.有移动类型.还有可复制的类型.

但是一个可以复制但有明确删除的移动构造函数的类型?我会说这样的课程是矛盾的.

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

  1. 明确删除移动构造函数应该意味着你无法移动它.

  2. 显式默认复制构造函数应该意味着你可以复制它(当然,为了这个对话的目的.我知道你仍然可以做一些事情,使显式默认值被删除).

  3. 如果可以复制类型,则可以移动它.这就是存在关于隐式删除的移动构造函数不参与重载解析的规则的原因.因此,移动是复制的适当子集.

在这种情况下,C++的行为是不一致的,因为您的代码是矛盾的.您希望您的类型可以复制但不可移动; C++不允许这样做,所以它表现得很奇怪.

看看当你消除矛盾时会发生什么.如果您明确删除了复制构造函数ExplicitDelete,那么一切都有意义.ImplicitDelete复制/移动构造函数被隐式删除,因此它是不动的.并且Implicit隐式删除了复制/移动构造函数,因此它也是不可移动的.

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

  • 这是有趣的."每个物体应该只是可移动的,或者既可移动又可复制,但不仅仅是可复制的." 我想这种语言可以让我自己在脚下射击,如果我明确的话(双关语).所以我的例子太合成了,真正不可移动的对象*真的应该*也是不可复制的,因为否则它没有任何实际意义.我想我喜欢你的回答.如果我找到一个不可移动但可复制的实际反例,我会回来的. (4认同)