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

时间:2015-11-29 22:31:08

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

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

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

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

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

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

然后,包含/继承自Implicit的类ImplicitDelete将生成一个非常精细的隐式移动构造函数,该函数会调用ImplicitDelete的复制文件。

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

在实践中,如果ImplicitImplicitDelete有一些重型可移动成员(想想vector<string>),我认为没有理由Implicit应该远远优于ImplicitDelete {1}}移动表现。 ImplicitDelete仍然可以将ExplicitDelete从其隐式移动ctor中复制 - 就像ImplicitImplicitDelete一样。

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

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

    • ImplicitDelete变得不是move-constructible,就像ExplicitDelete
    • 一样
    • ImplicitDelete已删除的移动ctor会导致Implicit中删除的隐式移动ctor(与ExplicitDelete执行ImplicitDelete的方式相同<) / LI>
    • Implicit变为move-constructible
    • 我的代码示例
    • std::move行的编译完全失败
  2. 或者,编译器回退到ExplicitDelete复制ctor

      所有ExplicitDelete中都会调用
    • move的复制构造函数,就像ImplicitDelete
    • 一样
    • ImplicitDelete得到了正确的隐含动作ctor
    • Implicit在此方案中未更改)
    • 代码示例的输出表明Explicit成员始终被移动。
  3. 以下是完整的工作示例:

    #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;
    }
    

1 个答案:

答案 0 :(得分:5)

  

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

理由是:您描述的案例没有意义。

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

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

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

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

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

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

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

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

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

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