为什么在堆数组初始化中调用了两次复制构造函数?

迈克尔扬

对于以下 C++14 代码,为什么 g++ 生成的代码new A[1]{x}似乎调用了复制构造函数两次?

#include <iostream>
using namespace std;

class A {
public:
    A()           { cout << "default ctor" << endl; }
    A(const A& o) { cout << "copy ctor" << endl;    }
    ~A()          { cout << "dtor" << endl;         }
};

int main()
{
    A x;
    cout << "=========" << endl;
    A* y = new A[1]{x};
    cout << "=========" << endl;
    delete[] y;
    return 0;
}

编译和输出:

$ g++ -fno-elide-constructors -std=c++14 test.cpp && ./a.out
default ctor
=========
copy ctor
copy ctor
dtor
=========
dtor
dtor

有趣的是,对于相同的代码,clang++ 只调用一次复制构造函数:

$ clang++ -fno-elide-constructors -std=c++14 test.cpp && ./a.out
default ctor
=========
copy ctor
=========
dtor
dtor

此外,在使用 g++ 时,将该A* y = new A[1]{x};更改为以下任何一项都会导致复制构造函数仅被调用一次:

  • A* y = new A {x}; - 普通堆对象而不是大小为 1 的堆数组
  • A y[1] {x}; - 堆栈上的数组而不是堆

所以看起来双拷贝构造函数行为只在堆数组初始化中表现出来。

泽塔

TL;DR:这可能是 GCC 缺陷,{x}在这种情况下被误解为暂时的。对于 中的每个元素new A[N]{x1, x2, ... xN},复制构造函数应该根据[decl.init]被调用一次[new.expr]相反,GCC可能将其解释为初始化列表,因此部分解释为中间右值。不过,我们可以强制 GCC 以其他方式解释它。


为什么 g++ 生成的代码new A[1]{x}似乎两次调用复制构造函数?

因为没有移动构造函数。如果我们添加一个移动构造函数和更多输出,我们可以更好地了解情况(编译器资源管理器):

#include <iostream>
using namespace std;

class A {
public:
    A()           { cout << "default ctor @" << this << endl; }
    A(A&& o)      { cout << "move ctor: " << &o << " to " << this << endl;    }
    A(const A& o) { cout << "copy ctor: " << &o << " to " << this << endl;    }
    ~A()          { cout << "dtor @" << this << endl;         }
};

int main()
{
    A x;
    cout << "=========" << endl;
    A* y = new A[1]{x};
    cout << "=========" << endl;
    delete[] y;
    return 0;
}

请注意,我们新A(A&&)构造函数的存在向我们展示了中间临时:

default ctor @0x7ffec28b5476
=========
copy ctor: 0x7ffec28b5476 to 0x7ffec28b5477
move ctor: 0x7ffec28b5477 to 0x55d0a7fa6288
dtor @0x7ffec28b5477
=========
dtor @0x55d0a7fa6288
dtor @0x7ffec28b5476

事实上,如果我们A(A&&) = delete构造函数,g++甚至不会再编译它(但 Clang 仍然接受它)。

似乎 g++ 误解了支撑初始化列表恕我直言,[expr.new] 可能允许这种解释,但这似乎是一个 g++ 缺陷,可能应该这样报告。

然而,整个磨难让我想起了我的一个老问题(初始化时真的需要花括号吗?)。因此,让我们引入更多大括号以确保g++不会误解我们的初始化程序:

int main()
{
    A x;
    cout << "=========" << endl;
    A* y = new A[1]{{{x}}};
    cout << "=========" << endl;
    delete[] y;
    return 0;
}

这个变体规避了 g++ 的行为:

initializer for T[1]     start : {
initializer for first element  : {
actual initializer for A       : {x}

然后程序输出是 ( Explorer )

default ctor @0x7ffede3d9967
=========
copy ctor: 0x7ffede3d9967 to 0x1eb0ec8
=========
dtor @0x1eb0ec8
dtor @0x7ffede3d9967

因此,对于多个元素,我们最终会陷入困境(编译器资源管理器):

int main()
{
    A x;
    cout << "=========" << endl;
    A* y = new A[2]{{{x},{{x}}};
    cout << "=========" << endl;
    delete[] y;
    return 0;
}

同样,没有调用额外的构造函数:

default ctor @0x7fff3a2a7a27
=========
copy ctor: 0x7fff3a2a7a27 to 0x1f49ec8
copy ctor: 0x7fff3a2a7a27 to 0x1f49ec9
=========
dtor @0x1f49ec9
dtor @0x1f49ec8
dtor @0x7fff3a2a7a27

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

为什么在下面的代码中两次调用了复制构造函数?

来自分类Dev

为什么在这段代码片段中复制构造函数被调用了两次?

来自分类Dev

为什么对象的初始化调用复制构造函数?

来自分类Java

ViewScope构造函数调用了两次,不确定为什么

来自分类Dev

为什么 C++ 复制构造函数被调用两次?

来自分类Dev

为什么 C++ 复制构造函数被调用两次?

来自分类Dev

为什么useState挂钩两次调用初始化值代码?

来自分类Dev

在MSVC上的数组初始化期间调用了没有复制或移动构造函数的析构函数

来自分类Dev

为什么这个构造函数被调用两次?

来自分类Dev

为什么我的构造函数被两次调用

来自分类Dev

为什么构造函数被两次调用

来自分类Dev

为什么在storage.single中两次调用了我的函数?

来自分类Dev

为什么类的构造函数被调用了四次,而在程序即将结束的时候,析构函数只被调用了两次?

来自分类Dev

为什么在此示例中调用了复制构造函数?

来自分类Dev

无法在复制构造函数中初始化数组

来自分类Dev

为什么在我的程序中两次调用构造函数?

来自分类Dev

为什么在Java中两次使用构造函数

来自分类Dev

复制初始化:为什么即使关闭复制删除功能也未调用move或copy构造函数?

来自分类Dev

为什么在构造函数初始化程序中初始化 const char 数组成员不兼容?

来自分类Dev

为什么析构函数被调用两次而构造函数仅被调用一次?

来自分类Dev

为什么final可以在构造函数中初始化?

来自分类Dev

为什么在 c++11 中调用了复制构造函数而不是移动构造函数?

来自分类Dev

初始化函数在lazy_static块中被调用两次

来自分类Dev

数据表针对函数调用初始化两次或多次

来自分类Dev

为什么DetermineState:forRegion:方法被调用了两次

来自分类Dev

数据初始化函数执行两次

来自分类Dev

为什么const成员可以初始化两次?

来自分类Dev

为什么将Spring bean初始化两次

来自分类Dev

为什么引导工具提示没有初始化两次?

Related 相关文章

  1. 1

    为什么在下面的代码中两次调用了复制构造函数?

  2. 2

    为什么在这段代码片段中复制构造函数被调用了两次?

  3. 3

    为什么对象的初始化调用复制构造函数?

  4. 4

    ViewScope构造函数调用了两次,不确定为什么

  5. 5

    为什么 C++ 复制构造函数被调用两次?

  6. 6

    为什么 C++ 复制构造函数被调用两次?

  7. 7

    为什么useState挂钩两次调用初始化值代码?

  8. 8

    在MSVC上的数组初始化期间调用了没有复制或移动构造函数的析构函数

  9. 9

    为什么这个构造函数被调用两次?

  10. 10

    为什么我的构造函数被两次调用

  11. 11

    为什么构造函数被两次调用

  12. 12

    为什么在storage.single中两次调用了我的函数?

  13. 13

    为什么类的构造函数被调用了四次,而在程序即将结束的时候,析构函数只被调用了两次?

  14. 14

    为什么在此示例中调用了复制构造函数?

  15. 15

    无法在复制构造函数中初始化数组

  16. 16

    为什么在我的程序中两次调用构造函数?

  17. 17

    为什么在Java中两次使用构造函数

  18. 18

    复制初始化:为什么即使关闭复制删除功能也未调用move或copy构造函数?

  19. 19

    为什么在构造函数初始化程序中初始化 const char 数组成员不兼容?

  20. 20

    为什么析构函数被调用两次而构造函数仅被调用一次?

  21. 21

    为什么final可以在构造函数中初始化?

  22. 22

    为什么在 c++11 中调用了复制构造函数而不是移动构造函数?

  23. 23

    初始化函数在lazy_static块中被调用两次

  24. 24

    数据表针对函数调用初始化两次或多次

  25. 25

    为什么DetermineState:forRegion:方法被调用了两次

  26. 26

    数据初始化函数执行两次

  27. 27

    为什么const成员可以初始化两次?

  28. 28

    为什么将Spring bean初始化两次

  29. 29

    为什么引导工具提示没有初始化两次?

热门标签

归档