复制初始化和直接初始化在C ++中有区别吗?

假设我有这个function:

void my_test() { A a1 = A_factory_func(); A a2(A_factory_func()); double b1 = 0.5; double b2(0.5); A c1; A c2 = A(); A c3(A()); } 

在每个分组中,这些陈述是否相同? 还是有一些额外的(可能优化)副本的一些初始化?

我看到有人说这两样东西。 请引用文字作为certificate。 另外请添加其他情况。

 A a1 = A_factory_func(); A a2(A_factory_func()); 

取决于A_factory_func()返回的types。 我假设它返回一个A – 然后它是做同样的 – 除了当复制构造函数是明确的,那么第一个将失败。 阅读8.6 / 14

 double b1 = 0.5; double b2(0.5); 

这是因为它是一个内置的types(这意味着这里不是类的types)。 阅读8.6 / 14 。

 A c1; A c2 = A(); A c3(A()); 

这是不一样的。 第一个缺省值 – 如果A是非POD,则不初始化,并且不对POD执行任何初始化(阅读8.6 / 9 )。 第二个副本初始化:值 – 初始化临时值,然后将该值复制到c2 (请参阅5.2.3 / 2和8.6 / 14 )。 这当然需要一个非显式的拷贝构造函数(请参阅8.6 / 14和12.3.1 / 3和13.3.1.3/1 )。 第三个为函数c3创build一个函数声明,该函数返回一个A ,并将函数指针指向返回A的函数(读取8.2 )。


深入到初始化直接和复制初始化

虽然他们看起来是一样的,并应该这样做,但这两种forms在某些情况下是显着不同的。 初始化的两种forms是直接和复制初始化:

 T t(x); T t = x; 

有我们可以归因于他们每个人的行为:

  • 直接初始化就像一个重载函数的函数调用:在这种情况下,函数是T的构造函数(包括explicit的),参数是x 。 重载parsing会find最匹配的构造函数,在需要的时候会做任何隐式的转换。
  • 复制初始化构造一个隐式转换序列:它试图将x转换为Ttypes的对象。 (然后它可以将该对象复制到初始化对象中,因此也需要复制构造函数 – 但这在下面并不重要)

正如你所看到的, 复制初始化在某种程度上是可能的隐式转换的直接初始化的一部分:虽然直接初始化可以调用所有的构造函数,另外还可以进行任何隐式转换,它需要匹配参数types,复制初始化可以只设置一个隐式转换序列。

我努力了,并得到了下面的代码来输出不同的文本为每个这些forms ,而不使用“明显”通过explicit构造函数。

 #include <iostream> struct B; struct A { operator B(); }; struct B { B() { } B(A const&) { std::cout << "<direct> "; } }; A::operator B() { std::cout << "<copy> "; return B(); } int main() { A a; B b1(a); // 1) B b2 = a; // 2) } // output: <direct> <copy> 

它是如何工作的,为什么会输出这个结果呢?

  1. 直接初始化

    它首先不知道转换。 它只会尝试调用构造函数。 在这种情况下,以下构造函数可用,并且是完全匹配的

     B(A const&) 

    没有转换,更不用说用户定义的转换,需要调用该构造函数(请注意,在这里也不会发生const限定转换)。 所以直接初始化会调用它。

  2. 复制初始化

    如上所述,复制初始化将构造一个转换序列,当a没有typesB或从它派生(显然是这里的情况)。 所以它会寻找转换的方法,并会find以下候选人

     B(A const&) operator B(A&); 

    注意我是如何重写转换函数的:参数types反映了this指针的types,它在非const成员函数中是非const的。 现在,我们把这些候选人用x作为参数。 获胜者是转换函数:因为如果我们有两个候选函数都接受同一types的引用,那么const较小版本就会胜出(顺便说一下,这也是非非const成员函数调用的机制-const对象)。

    请注意,如果我们将转换函数更改为一个const成员函数,那么转换是不明确的(因为两者的参数types都是A const& then):Comeau编译器会正确拒绝它,但GCC会以非迂回模式接受它。 切换到-pedantic使输出适当的歧义警告,但也。

我希望这有助于更清楚地说明这两种forms的区别。

赋值初始化不同。

以下两行都进行初始化 。 一个构造函数调用完成:

 A a1 = A_factory_func(); // calls copy constructor A a1(A_factory_func()); // calls copy constructor 

但它不等同于:

 A a1; // calls default constructor a1 = A_factory_func(); // (assignment) calls operator = 

目前我没有一个文本来certificate这一点,但实验很简单:

 #include <iostream> using namespace std; class A { public: A() { cout << "default constructor" << endl; } A(const A& x) { cout << "copy constructor" << endl; } const A& operator = (const A& x) { cout << "operator =" << endl; return *this; } }; int main() { A a; // default constructor A b(a); // copy constructor A c = a; // copy constructor c = b; // operator = return 0; } 

double b1 = 0.5; 是构造函数的隐式调用。

double b2(0.5); 是明确的呼叫。

看看下面的代码,看看不同之处:

 #include <iostream> class sss { public: explicit sss( int ) { std::cout << "int" << std::endl; }; sss( double ) { std::cout << "double" << std::endl; }; }; int main() { sss ddd( 7 ); // calls int constructor sss xxx = 7; // calls double constructor return 0; } 

如果你的类没有明确的构造函数,那么明确和隐含的调用是相同的。

首先分组:取决于A_factory_func返回的内容。 第一行是复制初始化的例子,第二行是直接初始化 。 如果A_factory_func返回一个A对象然后它们是等价的,那么它们都会调用A的拷贝构造函数,否则第一个版本会从A_factory_func的返回types或适当的A构造函数的可用转换运算符中创buildtypesA的右值,然后调用复制构造函数从这个临时构造a1 。 第二个版本试图find一个合适的构造函数,该构造函数接受A_factory_func返回的值,或者将返回值隐式转换为的值。

第二类:完全相同的逻辑成立,除了内置的types没有任何外来的构造函数,所以它们在实践中是相同的。

第三组: c1是默认初始化的, c2是从临时初始化的值复制初始化的。 如果用户提供的默认构造函数(如果有的话)没有明确地初始化它们,那么具有podtypes(或成员等等)的c1任何成员都不能被初始化。 对于c2 ,它取决于是否有用户提供的拷贝构造函数,以及是否适当地初始化这些成员,但是临时的成员将全部被初始化(如果未被明确初始化,则为零初始化)。 当litb发现, c3是一个陷阱。 这实际上是一个函数声明。

注意:

[12.2 / 1] Temporaries of class type are created in various contexts: ... and in some initializations (8.5).

即,复制初始化。

[12.8 / 15] When certain criteria are met, an implementation is allowed to omit the copy construction of a class object ...

换句话说,一个好的编译器在避免时不会创build一个用于复制初始化的副本; 而是直接调用构造函数 – 就像直接初始化一样。

换句话说,在大多数情况下,复制初始化就像直接初始化<意见>,在可以理解的代码被写入的地方。 由于直接初始化可能导致任意的(因此可能是未知的)转换,所以我希望在可能的情况下始终使用复制初始化。 (实际上看起来像初始化的奖金。)</ opinion>

技术上的困难:[12.2 / 1以上] Even when the creation of the temporary object is avoided (12.8), all the semantic restrictions must be respected as if the temporary object was created.

很高兴我不写一个C ++编译器。

回答这个部分:

A c2 = A(); A c3(A());

由于大多数的答案都是pre-c ++ 11,所以我正在添加c ++ 11对此的解释:

一个简单types说明符(7.1.6.2)或types名说明符(14.6),后面跟着一个带括号的expression式列表,构造给定expression式列表的指定types的值。 如果expression式列表是单个expression式,则types转换expression式与相应的强制转换expression式(5.4)是等价的(在定义中,如果定义的话)。 如果指定的types是类types,则类types应该是完整的。 如果expression式列表指定了多个单个值,那么types应该是一个具有适当声明的构造函数(8.5,12.1)的类,并且expression式T(x1,x2,…)与声明T t (x1,x2,…); 对于一些发明的临时variablest,其结果是t的值作为一个前值。

所以优化或不是他们是相等的按照标准。 请注意,这与其他答案提到的一致。 只是引用标准为了正确而说的话。

很多这些情况都受到对象的实现,所以很难给出具体的答案。

考虑这种情况

 A a = 5; A a(5); 

在这种情况下,假定一个合适的赋值运算符和初始化构造函数接受一个整数参数,我如何实现所述方法会影响每一行的行为。 然而,其中的一个人在执行过程中调用另一个来消除重复的代码是常见的做法(尽pipe在这种情况下这样简单就没有真正的目的)。

编辑:正如其他答复中提到的,第一行将实际上调用复制构造函数。 考虑与赋值运算符有关的评论,作为与独立赋值相关的行为。

也就是说,编译器如何优化代码将会产生它自己的影响。 如果我有初始化构造函数调用“=”运算符 – 如果编译器没有优化,最上面的行将执行2跳,而不是最后一行。

现在,对于最常见的情况,您的编译器将通过这些情况进行优化,并消除这种低效率。 所以你所描述的所有不同的情况都会变得相同。 如果您想查看正在完成的工作,可以查看目标代码或编译器的汇编输出。