隐式构造函数转换的编译器优化
Compiler optimization of implicit constructor conversion
在下面的代码中,我希望调用A的构造函数,然后调用A的复制构造函数。然而,事实证明只有构造函数被调用。
// MSVC++ 2008
class A
{
public:
A(int i):m_i(i)
{
cout << "constructorn";
}
A(const A& a)
{
m_i = a.m_i;
cout << "copy constructorn";
}
private:
int m_i;
};
int main()
{
// only A::A() is called
A a = 1;
return 0;
}
我想编译器足够聪明,可以优化掉第二个调用,直接用构造函数初始化对象a。那么,它是标准定义的行为,还是只是实现定义的?
这是标准配置,但不涉及优化
事实上,我相信有一个优化涉及,但它仍然是完全标准的†
此代码:
A a = 1;
调用A
的转换构造函数††。A
具有单个转换构造函数A(int i)
,该构造函数允许从int
到A
的隐式转换。
如果在构造函数声明之前使用explicit
,则会发现代码无法编译。
class A
{
public:
explicit A(int i) : m_i(i) // Note "explicit"
{
cout << "constructorn";
}
A(const A& a)
{
m_i = a.m_i;
cout << "copy constructorn";
}
private:
int m_i;
};
void TakeA(A a)
{
}
int main()
{
A a = 1; // Doesn't compile
A a(1); // Does compile
TakeA(1); // Doesn't compile
TakeA(A(1)); // Does compile
return 0;
}
†再次查看标准后,我最初可能错了。
8.5初始化程序[dcl.init]
12.参数传递、函数中发生的初始化return,抛出异常(15.1),处理异常(15.3),并且大括号包围的初始值设定项列表(8.5.1)被调用复制初始化,相当于形式
T x = a;
14.初始化程序的语义如下。目标类型是正在初始化的对象或引用的类型source类型是初始值设定项表达式的类型。源类型当初始值设定项包含大括号或是带括号的表达式列表。
- 如果目标类型是(可能是cv限定的)类类型:
- 如果类是一个聚合(8.5.1),并且初始值设定项是一个包含大括号的列表,请参见8.5.1
- 如果初始化是直接初始化,或者如果是复制初始化,其中源类型的cv不合格版本与目标类是同一类,或者是目标类的派生类,则考虑构造函数。列举了适用的构造函数(13.3.1.3),并通过重载解析(13.3)选择了最好的构造函数。调用这样选择的构造函数来初始化对象,初始化器表达式作为其参数。如果没有应用构造函数,或者重载解析不明确,则初始化格式不正确
- 否则(即,对于剩余的副本初始化情况),如13.3.1.4所述,枚举可以从源类型转换为目标类型或(当使用转换函数时)转换为其派生类的用户定义转换序列,并通过过载解析(13.3)选择最佳转换序列。如果转换无法完成或不明确,初始化不正确。调用所选函数时,将初始值设定项表达式作为其参数如果函数是构造函数,则调用初始化目标类型的临时函数。调用的结果(对于构造函数来说是临时的)然后用于根据上面的规则直接初始化作为复制初始化目标的对象。在某些情况下,允许实现通过将中间结果直接构造到正在初始化的对象中来消除这种直接初始化中固有的复制;参见12.2、12.8
。。。
所以从某种意义上说,这在很大程度上是一种优化。但我并不担心它,因为它是标准明确允许的,而且现在几乎每个编译器都会执行省略。
有关初始化的更彻底的处理,请参阅GotW的这篇文章(#36)。文章似乎同意上述对标准的解释:
注意:在最后一种情况下("T t3=u;"),编译器可以调用用户定义的转换(创建临时对象)和T副本构造函数(从临时中构造t3),或者它可以选择删除临时并直接从u构建t3(这将最终等于"T t3(u);")。自1997年7月以来最终的标准草案,编译器可以省略临时对象已被限制,但仍然允许优化和返回值优化。
†
ISO/IEC 14882:2003 C++标准参考
12.3.1构造函数[class.conv.ctor]的转换
1.在没有函数说明符的情况下声明的构造函数
explicit
可以用单个参数调用的指定从将其第一个参数的类型设置为其类的类型。这样的构造函数被称为转换构造函数[示例:class X { // ... public: X(int); X(const char*, int =0); }; void f(X arg) { X a = 1; // a = X(1) X b = "Jessie"; // b = X("Jessie",0) a = 2; // a = X(2) f(3); // f(X(3)) }
-结束示例]
2.显式构造函数构造对象就像非显式构造函数一样构造函数,但仅当直接初始化语法(8.5)或明确使用强制型铸件(5.2.9、5.4)的情况下。默认值构造函数可以是显式构造函数;这样的构造函数用于执行默认初始化或值初始化(8.5)。[示例:
class Z { public: explicit Z(); explicit Z(int); // ... }; Z a; // OK: default-initialization performed Z a1 = 1; // error: no implicit conversion Z a3 = Z(1); // OK: direct initialization syntax used Z a2(1); // OK: direct initialization syntax used Z* p = new Z(1); // OK: direct initialization syntax used Z a4 = (Z)1; // OK: explicit cast used Z a5 = static_cast<Z>(1); // OK: explicit cast used
-结束示例]
此处没有优化。当=
用于初始化时,它相当于(几乎)用右手边作为参数调用构造函数。所以这个:
A a = 1;
(大部分)相当于:
A a(1);
- 如何解决gcc编译器优化导致的centos双编译器设置中的分段错误
- 使用仅使用一次的变量调用的复制构造函数.这可能是通过调用move构造函数进行编译器优化的情况吗
- 这个C++编译器优化(在自身的实例上调用对象自己的构造函数)的名称是什么,它是如何工作的?
- VS2017调试器:没有地址,可能是由于编译器优化
- 何时允许编译器优化复制构造函数
- 如何使用 GCC 编译器优化创建静态库?
- 为什么 std::chrono 在测量循环和编译器优化的并行 OpenMP 的执行时间时不起作用?
- 是否允许编译器优化掉局部易失性变量
- 删除编译器优化并在发布中启用 pdb 文件
- 静态 constexpr 的编译器优化
- 如何让MSVC编译器优化多步POD初始化?
- 按位不操作的编译器优化
- 模板专用化与编译器优化
- 编译器优化:G 比英特尔慢
- 运算符重载关联性编译器优化
- Intel OpenCL编译器:优化结构使用情况
- C 中编译器优化的影响
- 视觉C++ 2017 错误?编译器优化表达式
- 虚拟功能编译器优化C
- 未定义的行为确实有助于现代编译器优化生成的代码