Eri*_*c Z 2 c++ constructor implicit-conversion
在下面的代码中,我期望调用A的构造函数,然后是A的复制构造函数.然而,事实证明只有构造函数被调用.
// MSVC++ 2008
class A
{
public:
A(int i):m_i(i)
{
cout << "constructor\n";
}
A(const A& a)
{
m_i = a.m_i;
cout << "copy constructor\n";
}
private:
int m_i;
};
int main()
{
// only A::A() is called
A a = 1;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我猜编译器是足够聪明的优化掉了第二个电话,来初始化对象一个直接的构造.那么它是标准定义的行为还是只是实现定义的?
这是标准的,但没有涉及优化.
实际上,我认为有一个优化涉及,但它仍然完全是标准的.†
这段代码:
A a = 1;
Run Code Online (Sandbox Code Playgroud)
调用转换构造††的A.A有一个单一的转换构造A(int i),其允许隐式转换从int到A.
如果你在前面加上构造函数声明explicit,你会发现代码不会编译.
class A
{
public:
explicit A(int i) : m_i(i) // Note "explicit"
{
cout << "constructor\n";
}
A(const A& a)
{
m_i = a.m_i;
cout << "copy constructor\n";
}
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;
}
Run Code Online (Sandbox Code Playgroud)
†再次查看标准后,我可能最初错了.
8.5初始值设定项[dcl.init]
12.参数传递,函数返回,抛出异常(15.1),处理异常(15.3)和大括号括起初始化列表(8.5.1)时发生的初始化称为 复制初始化,相当于表单
Run Code Online (Sandbox Code Playgroud)T x = a;14.初始化器的语义如下.的目的地 类型是对象或引用的类型被初始化和 源类型是初始化表达式的类型.当初始化程序是大括号括起或者是带括号的表达式列表时,不定义源类型.
...
- 如果目标类型是(可能是cv限定的)类类型:
- 如果类是聚合(8.5.1),并且初始化程序是括号括起的列表,请参见8.5.1.
- 如果初始化是直接初始化,或者它是复制初始化,其中源类型的cv-nonqualified版本与目标类相同的类或派生类,则考虑构造函数.列举了适用的构造函数(13.3.1.3),并通过重载解析(13.3)选择最佳构造函数.调用如此选择的构造函数来初始化对象,初始化表达式作为其参数.如果没有构造函数适用,或者重载决策是不明确的,则初始化是错误的.
- 否则(即,对于剩余的复制初始化情况),可以如13.3中所述枚举可以从源类型转换为目的地类型或(当使用转换函数时)到其派生类的用户定义的转换序列. 1.4,通过重载决策(13.3)选择最好的一个.如果转换不能完成或不明确,则初始化是错误的.选择的函数以初始化表达式作为参数调用; 如果函数是构造函数,则调用初始化目标类型的临时函数.然后,根据上面的规则,调用的结果(对于构造函数的情况是临时的)用于直接初始化作为复制初始化目标的对象.在某些情况下,允许实现通过将中间结果直接构造到正在初始化的对象中来消除此直接初始化中固有的复制 ; 见12.2,12.8.
...
所以从某种意义上说,这是一种非常优化的方式.但我不担心它,因为标准明确允许它,现在几乎每个编译器都是elison.
有关初始化的更全面的处理,请参阅本文(#36).该文似乎同意上述标准的解释:
注意:在最后一种情况下("T t3 = u;"),编译器可以调用用户定义的转换(创建临时对象)和T复制构造函数(从临时构造t3),或者它可以选择消除临时性并直接从u构造t3(这最终将等同于"T t3(u);").自1997年7月以来,在最终草案标准中,编译器对于删除临时对象的自由度受到限制,但仍然允许进行此优化和返回值优化.
††
12.3.1按构造函数[class.conv.ctor]转换
1.声明没有 可以使用单个参数调用的函数说明符的 构造函数指定
explicit从其第一个参数的类型到其类的类型的转换.这样的构造函数称为转换构造函数.[例:Run Code Online (Sandbox Code Playgroud)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)的情况下才这样做.默认构造函数可以是显式构造函数; 这样的构造函数将用于执行默认初始化或valueinitialization(8.5). [例:
Run Code Online (Sandbox Code Playgroud)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- 末端的例子]