对于以下 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] 删除。
我来说两句