为什么隐式和显式删除move constructor不同的处理? [英] Why are implicitly and explicitly deleted move constructors treated differently?

查看:343
本文介绍了为什么隐式和显式删除move constructor不同的处理?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



Do C ++ 14 / C ++ 17改变什么? (C ++ 14中的 DR1402



注意:我明白发生了什么,我的理解是它是根据C ++ 11标准的规则,我对这些规则的含义这个行为的理由感兴趣(请make






假设一个类 ExplicitDelete 使用明确删除的move ctor和显式默认的copy ctor。这个类不是 move constructible ,即使一个兼容的 copy ctor可用,因为重载分辨率选择移动构造函数,并在编译时失败,由于



假设一个 ImplicitDelete 类包含或继承自 ExplicitDelete 并且什么也不做。由于 C ++ 11移动ctor规则,此类会将其move ctor隐式声明为已删除, a>。然而,这个类仍然会通过其复制ctor move constructible 。 (最后一条语句是否与 DR1402 的解决有关?)



然后一个类 Implicit 包含/继承自 ImplicitDelete 将有一个完美的隐式移动构造函数生成,调用 ImplicitDelete 的复制ctor。



允许隐式能够隐式移动和 ImplicitDelete 不能隐式移动的原因吗?



在实践中,如果 Implicit ImplicitDelete 成员(认为 vector< string> ),我没有理由 Implicit 应该远远优于 ImplicitDelete ImplicitDelete 仍然可以从其隐式移动ctor中复制 ExplicitDelete ,就像 Implicit 使用 ImplicitDelete






行为似乎不一致。如果这两个事情发生,我会发现它更一致:


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




    • ImplicitDelete c>

    • ImplicitDelete 删除的移动ctor导致在隐含中删除​​隐式移动ctor(以与 ExplicitDelete 相同的方式 ImplicitDelete

    • Implicit c / c>



  2. 或者,编译器还会为 ExplicitDelete




    • ExplicitDelete 的复制构造函数在所有 move ,就像 ImplicitDelete

    • ImplicitDelete 获得适当的隐式移动ctor

    • 隐含在此情况下未更改)

    • 代码示例的输出表示 Explicit 成员总是移动。







以下是完整的示例:

  #include< utility> 
#include< iostream>
using namespace std;

struct Explicit {
//打印包含类的移动或复制构造函数是否被调用
//在实践中,这将是昂贵的向量< string>
string owner;
显式(字符串owner):owner(owner){};
Explicit(const Explicit& o){cout< o.owner<< 实际上是复制\\\
; }
Explicit(Explicit& o)noexcept {cout<< o.owner<< is moving \\\
; }
};
struct ExplicitDelete {
ExplicitDelete()= default;
ExplicitDelete(const ExplicitDelete&)= default;
ExplicitDelete(ExplicitDelete&&)noexcept = delete;
};
struct ImplicitDelete:ExplicitDelete {
Explicit exp {ImplicitDelete};
};
struct隐式:ImplicitDelete {
Explicit exp {Implicit};
};

int main(){
ImplicitDelete id1;
ImplicitDelete id2(move(id1)); // expect copy call
Implicit i1;
Implicit i2(move(i1)); //期望1x ImplicitDelete的副本和1x Implicit的移动
return 0;
}


解决方案


因此,允许Implicit能够隐式移动和ImplicitDelete不能隐式移动的原因是什么?




看到,所有这一切都是因为 ExplicitDelete 。根据你的定义,这个类有一个明确删除的移动构造函数,但是一个默认的复制构造函数。



有不动的类型,既不复制也不移动。有只移动类型。有可复制的类型。



但是可以复制但是显式删除移动构造函数的类型?



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


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


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


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


C ++在此实例中的行为不一致,矛盾。您希望您的类型是可复制的,但不可移动; C ++不允许这样,所以它的行为奇怪。



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



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


What is the rationale behind the different treatment of implicitly and explicitly deleted move constructors in the C++11 standard, with respect to the implicit generation of move constructors of containing/inheriting classes?

Do C++14/C++17 change anything? (Except DR1402 in C++14)

Note: I understand what is happening, I understand that it is according to the C++11 standard's rules, I'm interested in the rationale for these rules that imply this behavior (please make sure not to simply restate that it is the way it is because the standard says so).


Assume a class ExplicitDelete with an explicitly deleted move ctor and an explicitly defaulted copy ctor. This class isn't move constructible even though a compatible copy ctor is available, because overload resolution chooses the move constructor and fails at compile time due to its deletion.

Assume a class ImplicitDelete which either contains or inherits from ExplicitDelete and does nothing else. This class will have its move ctor implicitly declared as deleted due to C++11 move ctor rules. However, this class will still be move constructible via its copy ctor. (Does this last statement have to do with resolution of DR1402?)

Then a class Implicit containing/inheriting from ImplicitDelete will have a perfectly fine implicit move constructor generated, that calls ImplicitDelete's copy ctor.

So what is the rationale behind allowing Implicit to be able to move implicitly and ImplicitDelete not to be able to move implicitly?

In practice, if Implicit and ImplicitDelete have some heavy-duty movable members (think vector<string>), I see no reason that Implicit should be vastly superior to ImplicitDelete in move performance. ImplicitDelete could still copy ExplicitDelete from its implicit move ctor—just like Implicit does with ImplicitDelete.


To me, this behavior seems inconsistent. I'd find it more consistent if either of these two things happened:

  1. The compiler treats both the implicitly and explicitly deleted move ctors the same:

    • ImplicitDelete becomes not move-constructible, just like ExplicitDelete
    • ImplicitDelete's deleted move ctor leads to a deleted implicit move ctor in Implicit (in the same way that ExplicitDelete does that to ImplicitDelete)
    • Implicit becomes not move-constructible
    • Compilation of the std::move line utterly fails in my code sample
  2. Or, the compiler falls back to copy ctor also for ExplicitDelete:

    • ExplicitDelete's copy constructor is called in all moves, just like for ImplicitDelete
    • ImplicitDelete gets a proper implicit move ctor
    • (Implicit is unchanged in this scenario)
    • The output of the code sample indicates that the Explicit member is always moved.


Here's the fully working example:

#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?

The rationale would be this: the case you describe does not make sense.

See, all of this started because of ExplicitDelete. By your definition, this class has an explicitly deleted move constructor, but a defaulted copy constructor.

There are immobile types, with neither copy nor move. There are move-only types. And there are copyable types.

But a type which can be copied but has an explicitly deleted move constructor? I would say that such a class is a contradiction.

Here are the three facts, as I see it:

  1. Explicitly deleting a move constructor is supposed to mean you can't move it.

  2. Explicitly defaulting a copy constructor is supposed to mean you can copy it (for the purposes of this conversation, of course. I know you can still do things that make the explicit default deleted instead).

  3. If a type can be copied, it can be moved. That's why the rule about implicitly deleted move constructors not participating in overload resolution exists. Therefore, movement is a proper subset of copying.

The behavior of C++ in this instance is inconsistent because your code is contradictory. You want your type to be copyable but not moveable; C++ does not allow that, so it behaves oddly.

Look at what happens when you remove the contradiction. If you explicitly delete the copy constructor in ExplicitDelete, everything makes sense again. ImplicitDelete's copy/move constructors are implicitly deleted, so it is immobile. And Implicit's copy/move constructors are implicitly deleted, so it too is immobile.

If you write contradictory code, C++ will not behave in an entirely legitimate fashion.

这篇关于为什么隐式和显式删除move constructor不同的处理?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆