【问题标题】:Why are copy and move constructors called together? [duplicate]为什么复制和移动构造函数一起调用? [复制]
【发布时间】:2018-02-03 10:19:43
【问题描述】:

考虑以下代码:

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

class A
{
public:
     A(int) { cout << "int" << endl; }
     A(A&&) { cout << "move" << endl; }
     A(const A&) { cout << "copy" << endl; }
};

int main()
{
    vector<A> v
    {
        A(10), A(20), A(30)
    };

    _getch();
    return 0;
}

输出是:

int
int
int
copy
copy
copy

A(10)A(20)A(30) 是临时的,对吧?

那么为什么要调用复制构造函数呢?不应该调用移动构造函数吗?

改为传递move(A(10))move(A(20))move(A(30)),输出为:

int
move
int
move
int
move
copy
copy
copy

在这种情况下,复制或移动构造函数被调用。

发生了什么事?

【问题讨论】:

  • 您不能从initializer_list 元素中移动。第二个示例中的附加 move 禁止复制省略,因此您可以在副本顶部进行额外移动。
  • std::vector 中采用std::initializer_list 的构造函数中,这些元素不再是右值。
  • 此外,如果您要调用v.push_back() 几次,直到向量超出其容量并不得不重新分配,您将得到一堆副本而不是移动。为防止这种情况发生,您需要标记您的移动构造函数noexcept
  • @ArneVogel:我被撕裂了。一方面,该问题的“更新”与这个问题非常相似。另一方面,这些答案非常不完整。

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


【解决方案1】:

std::vector 可以从std::initializer_list 构造,并且您正在调用该构造函数。 initializer_list 构造的规则表明此构造函数是积极首选的:

一个构造函数是一个initializer-list构造函数,如果它的第一个参数是std::initializer_list&lt;E&gt;类型 或对某些类型 E 的可能具有 cv 资格的 std::initializer_list&lt;E&gt; 的引用,并且要么有 没有其他参数或所有其他参数都有默认参数(8.3.6)。 [注:初始化列表 构造函数在列表初始化中优于其他构造函数<...>]

此外,由于 initializer_list 作为在后台分配的数组的奇怪实现,std::initializer_list&lt;E&gt; 引用的相应数组的元素被强制复制初始化(可以省略):

std::initializer_list&lt;E&gt; 类型的对象是从初始化列表构造的,就像实现一样 分配了一个N 类型的E 元素数组,其中N 是初始化器列表中的元素数。 该数组的每个元素都使用初始化列表的相应元素进行复制初始化,并且 构造 std::initializer_list&lt;E&gt; 对象以引用该数组

(以上两个参考都来自 N3337 [dcl.init.list])

但是,在您的第一个示例中,尽管名称为 ([dcl.init]/14),但副本可以/被省略,因此您看不到额外的副本结构(它们也可以移动) 你可以感谢你的编译器,因为在 C++11 中不需要复制省略(尽管它在 C++17 中)。

参见 [class.copy] 了解更多详情(“当满足某些条件时,允许实现省略类的复制/移动构造 对象...")。

最后一部分是关键:

[support.initlist] 声明

initializer_list&lt;E&gt; 类型的对象提供对const E 类型对象数组的访问。

这意味着std::vector不能直接接管内存;它必须被复制,这是您最终看到复制结构被调用的地方。

在第二个示例中,正如 Kerrek SB 所说,您阻止了我之前提到的复制省略,并导致了额外的移动开销。

【讨论】:

  • 似乎vector(std::initializer_list&amp;&amp;) 会很有用:(
【解决方案2】:

A(10), A(20), A(30) 是临时的吧?

正确。

那么为什么要调用复制构造函数呢?不应该调用移动构造函数吗?

不幸的是,无法从std::initializer_list 移动,这是std::vector 的构造函数所使用的。

传递 move(A(10)), move(A(20)), move(A(30)) 代替

在这种情况下,复制或移动构造函数被调用。发生了什么事?

因为std::move 转换可以防止复制省略,所以std::initializer_list 的元素是移动构造的,没有省略。然后向量的构造函数从列表中复制。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-01-24
    • 2015-06-10
    • 1970-01-01
    • 2013-04-25
    相关资源
    最近更新 更多