【问题标题】:Why is derived class move constructible when base class isn't?当基类不是时,为什么派生类可以移动构造?
【发布时间】:2017-01-01 12:29:12
【问题描述】:

考虑以下示例:

#include <iostream>
#include <string>
#include <utility>

template <typename Base> struct Foo : public Base {
    using Base::Base;
};

struct Bar {
    Bar(const Bar&) { }
    Bar(Bar&&) = delete;
};

int main() {
    std::cout << std::is_move_constructible<Bar>::value << std::endl; // NO
    std::cout << std::is_move_constructible<Foo<Bar>>::value << std::endl; // YES. Why?!
}

为什么编译器生成一个移动构造函数,尽管基类是不可移动构造的?

这是标准还是编译器错误?是否可以“完美地传播”将构造从基类移动到派生类?

【问题讨论】:

  • 好吧,除非你坚持编写可复制但不可移动的类,否则你不会遇到这个问题,而且没有理由这样做。
  • 这个问题背后的想法是创建非侵入式包装类的可能性,它表现出与它们的基础完全相同的外部行为。附言很难选择接受哪个答案(我不能同时接受,对吧?):)
  • @Dmitry:不,您不能接受多个,但您应该对任何有助于您理解问题的合理尝试投赞成票。顺便说一句,很好的问题暴露了不直观的行为!
  • @Brian: class FamousPaintingInTheLouvre ?
  • @einpoklum 你不会删除那种类的移动构造函数,你只会让右值的构造使用复制构造函数。

标签: c++ c++11 language-lawyer move-semantics move-constructor


【解决方案1】:

因为:

重载决议忽略定义为已删除的默认移动构造函数。

([class.copy]/11)

Bar 的移动构造函数被显式删除,因此Bar 无法移动。但是Foo&lt;Bar&gt; 的移动构造函数在被隐式声明为默认值后被隐式删除,因为Bar 成员无法移动。因此Foo&lt;Bar&gt; 可以使用其复制构造函数移动。

编辑:我还忘了提到一个重要的事实,即using Base::Base 等继承构造函数声明不会继承默认、复制或移动构造函数,这就是为什么Foo&lt;Bar&gt; 没有继承显式删除的移动构造函数来自Bar

【讨论】:

  • 嗯,我不明白这个。原因似乎是“但是 Foo 的移动构造函数在被隐式声明为默认值后被隐式删除,因为 Bar 成员无法移动。因此 Foo 可以使用其复制构造函数移动。 "正如你所描述的。那么,为什么它不认为“Bar 的移动构造函数被显式删除,因此 Bar 可以使用它的复制构造函数移动”?
  • 我认为“因此Foo&lt;Bar&gt; 可以使用其复制构造函数移动。”措辞非常混乱。以一种不暗示对象正在移出的方式改写可能会更好,只有(示例)调用Foo&lt;Bar&gt;(std::move(other)) 是合法的。什么的。
  • @AndreasPapadopoulos IMO 最好说“Foo&lt;Bar&gt; 可以使用其复制 ctor 构造移动”。
  • @Andreas 对于像我这样的普通人来说,“移动构造”和“移动到”之间似乎没有区别。如果对于一个基类,谓词A&lt;Base&gt; 不成立,那么对于派生类来说似乎很混乱,它需要对A&lt;Base&gt; 断言的Base 对象进行操作以声明其可能性,谓词突然产生true。如果这属于这些谓词的“缺点”,对我来说似乎是合乎逻辑的,它们可能会错过在“非直接上下文”中发生的错误,比如侧面实例等。但如果有概念上的原因,我还不明白它。
  • @JohannesSchaub-litb:两者的关键区别在于Base显式删除了移动ctor(->移动时编译时错误)和A&lt;Base&gt;隐式删除它,因为该子句中指定的内容未完全由宋元瑶命名。由于在尝试移动 A&lt;Base&gt; 的实例时不考虑 隐式 已删除的移动 ctor,并且因为 A&lt;Base&gt;::A(A const&amp;) 是移动时唯一要调用的 ctor,所以我理解为什么谓词是 @ 987654340@ 用于派生,false 用于基础。这真是令人困惑 - 但是,嘿,直觉,对吧?
【解决方案2】:

1. std::is_move_constructible的行为

这是std::is_move_constructible 的预期行为:

没有移动构造函数但具有接受const T&amp;参数的复制构造函数的类型,满足std::is_move_constructible

这意味着使用复制构造函数仍然可以从右值引用T&amp;&amp; 构造T。而Foo&lt;Bar&gt; 有一个Implicitly-declared copy constructor

2。 Foo&lt;Bar&gt;的隐式声明的移动构造函数

为什么编译器生成移动构造函数,尽管基类是不可移动构造的?

其实Foo&lt;Bar&gt;的move构造函数定义为deleted,但是注意删除的隐式声明的move构造函数会被重载解析忽略。

T 类的隐式声明或默认移动构造函数是 定义为删除以下任何一项为真:

...
T has direct or virtual base class that cannot be moved (has deleted, inaccessible, or ambiguous move constructors); 
...

重载决议忽略已删除的隐式声明的移动构造函数(否则它将阻止从右值进行复制初始化)。

3. BarFoo&lt;Bar&gt; 之间的不同行为

注意Bar 的移动构造函数显式声明为deletedFoo&lt;Bar&gt; 的移动构造函数被隐式声明并定义为deleted。关键是重载决议忽略了已删除的隐式声明的移动构造函数,这使得移动构造Foo&lt;Bar&gt; 及其复制构造函数成为可能。但是显式删除的移动构造函数会参与重载决议,这意味着当试图移动构造函数Bar时,删除的移动构造函数将被选中,那么程序是非良构的。

这就是为什么Foo&lt;Bar&gt; 可以移动构造而Bar 不是。

标准对此有明确的声明。 $12.8/11 Copying and moving class objects [class.copy]

定义为已删除的默认移动构造函数被重载决议([over.match],[over.over])忽略。 [注意:删除的移动构造函数会干扰右值的初始化,而右值可以使用复制构造函数。 ——尾注]

【讨论】:

  • 我不清楚这个答案在哪里解决了 BarFoo&lt;Bar&gt; 类之间的行为差​​异。
  • 嗯...我还是不太清楚。为什么被显式删除的Bar 的移动构造函数被重载决议忽略 也不正确?我会猜的。虽然被删除了,但它仍然存在,参与重载决议,并且当它匹配时,宾果游戏,程序格式不正确。注释eel.is/c++draft/dcl.fct.def.delete 第 2 点似乎是这么说的。标准委员会当然应该因为发明了令人困惑的术语而获得奖励(这里“删除”是为了继续存在的东西;像“禁止”或“中毒”这样的东西会更具暗示性)。
  • @MarcvanLeeuwen 隐式声明的移动构造函数即使被删除也存在。所以我认为委员会只想区分显式删除隐式删除。如果您明确地deleted 它,则假定该类根本不支持移动语义,包括移动构造。
  • @MarcvanLeeuwen:因为你明确声明忽略它。否则将无法明确表示您的类型根本不可移动。
  • @MarcvanLeeuwen:这真是令人困惑......像这样的东西在我眼中是 C++ 的诅咒。
猜你喜欢
  • 2021-08-25
  • 2020-04-07
  • 2018-06-28
  • 2011-02-24
  • 2014-06-23
  • 2013-08-21
  • 1970-01-01
  • 1970-01-01
  • 2017-12-18
相关资源
最近更新 更多