Is there a difference between copy initialization and direct initialization?
我想这个功能:
1 2 3 4 5 6 7 8 9 10 11 12 | 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()); } |
在每个分组的报表,这些是相同的吗?或有额外的(可能是优化的复制和初始化)?
我有两个湖泊的人说的事情。请引用文本作为证据。所以,请添加其他案件。
C++ 17更新
在C++ 17中,EDCOX1(0)的含义从创建临时对象(C++<=14)变为仅指定在C++ 17中对该表达式初始化为(松散地说)的任何对象的初始化。这些对象(称为"结果对象")是由声明(如
在我们的例子中,在
1 2 | A a1 = A_factory_func(); A a2(A_factory_func()); |
取决于
1 2 | double b1 = 0.5; double b2(0.5); |
这样做是因为它是一个内置类型(这意味着这里不是类类型)。读数为86/14。好的。
1 2 3 | A c1; A c2 = A(); A c3(A()); |
这样做是不一样的。如果
深入了解初始化直接和复制初始化好的。
虽然它们看起来是相同的,应该是相同的,但在某些情况下,这两种形式是显著不同的。初始化的两种形式是直接初始化和复制初始化:好的。
1 2 | T t(x); T t = x; |
我们可以将行为归因于它们中的每一个:好的。
- 直接初始化的行为类似于对重载函数的函数调用:在这种情况下,函数是
T 的构造函数(包括explicit 的构造函数),参数是x 。重载解析将找到最佳匹配的构造函数,并在需要时执行所需的任何隐式转换。 - 复制初始化构造一个隐式转换序列:它试图将
x 转换为T 类型的对象。(然后它可以将该对象复制到to-initialized对象中,因此也需要一个复制构造函数-但这在下面并不重要)
如您所见,复制初始化在某种程度上是关于可能的隐式转换的直接初始化的一部分:虽然直接初始化有所有可调用的构造函数,而且可以执行任何隐式转换,以匹配参数类型,但复制初始化只能设置一个隐式转换序列。好的。
我尝试了一下,得到了以下代码来为每个表单输出不同的文本,而不使用通过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #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&) |
调用该构造函数不需要转换,更不用说用户定义的转换(注意这里也没有常量限定转换)。所以直接初始化会调用它。好的。
复制初始化好的。
如上所述,当
1 2 | B(A const&) operator B(A&); |
请注意,我如何重写转换函数:参数类型反映了
注意,如果我们将转换函数更改为常量成员函数,那么转换是不明确的(因为这两个函数的参数类型都是
我希望这有助于更清楚地说明这两种形式的区别!好的。好啊。
型
分配不同于初始化。
以下两行都进行初始化。单个构造函数调用完成:
1 2 | A a1 = A_factory_func(); // calls copy constructor A a1(A_factory_func()); // calls copy constructor |
但这并不等同于:
1 2 | A a1; // calls default constructor a1 = A_factory_func(); // (assignment) calls operator = |
号
我现在没有文本来证明这一点,但是很容易实验:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #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; } |
型
请查看以下代码以查看区别:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #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; } |
如果类没有显式构造,则显式和隐式调用是相同的。
值得注意的:
[12.2/1]
即,用于复制初始化。
[12.8/15]
换句话说,当可以避免复制初始化时,一个好的编译器不会创建一个副本;相反,它只会直接调用构造函数——即,就像直接初始化一样。
换句话说,在大多数情况下,复制初始化就像直接初始化一样,其中编写了可理解的代码。由于直接初始化可能会导致任意(因此可能是未知)转换,因此我希望在可能的情况下始终使用复制初始化。(额外的好处是,它实际上看起来像初始化。)
技术类别:[12.2/1从上接]
很高兴我没有编写C++编译器。
关于本部分的回答:
A c2 = A(); A c3(A());
因为大多数答案都是前C ++ 11,所以我添加了C++ 11对此的说法:
A simple-type-specifier (7.1.6.2) or typename-specifier (14.6)
followed by a parenthesized expression-list constructs a value of the
specified type given the expression list. If the expression list is a
single expression, the type conversion expression is equivalent (in
definedness, and if defined in meaning) to the corresponding cast
expression (5.4). If the type specified is a class type, the class
type shall be complete. If the expression list specifies more than a
single value, the type shall be a class with a suitably declared
constructor (8.5, 12.1), and the expression T(x1, x2, ...) is
equivalent in effect to the declaration T t(x1, x2, ...); for some
invented temporary variable t, with the result being the value of t as
a prvalue.
因此,无论优化与否,它们都是符合标准的等价物。请注意,这与其他答案所提到的是一致的。为了正确起见,只需引用标准所说的话。
型
第一组:取决于
第二组:完全相同的逻辑,只是内置类型没有任何外来的构造函数,所以实际上它们是相同的。
第三组:
初始化对象时,可以看到它在
Classes:
1 2 3 4 5 6 7 8 9 10 11 | class A { A(int) { } // converting constructor A(int, int) { } // converting constructor (C++11) }; class B { explicit B(int) { } explicit B(int, int) { } }; |
在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | int main() { A a1 = 1; // OK: copy-initialization selects A::A(int) A a2(2); // OK: direct-initialization selects A::A(int) A a3 {4, 5}; // OK: direct-list-initialization selects A::A(int, int) A a4 = {4, 5}; // OK: copy-list-initialization selects A::A(int, int) A a5 = (A)1; // OK: explicit cast performs static_cast // B b1 = 1; // error: copy-initialization does not consider B::B(int) B b2(2); // OK: direct-initialization selects B::B(int) B b3 {4, 5}; // OK: direct-list-initialization selects B::B(int, int) // B b4 = {4, 5}; // error: copy-list-initialization does not consider B::B(int,int) B b5 = (B)1; // OK: explicit cast performs static_cast } |
默认情况下,构造函数为
1 2 | A a1 = 1; // this is copy initialization A a2(2); // this is direct initialization |
通过将一个结构定义为
1 2 | B b2(2); // this is direct initialization B b5 = (B)1; // not problem if you either use of assign to initialize and cast it as static_cast |
型
很多这样的情况都取决于对象的实现,因此很难给出具体的答案。
考虑一下这个案子
1 2 | A a = 5; A a(5); |
。
在这种情况下,假设有一个合适的赋值运算符&初始化接受单个整型参数的构造函数,我如何实现所述方法会影响每行的行为。但是,在实现中,其中一个调用另一个以消除重复代码是常见的做法(尽管在这样简单的情况下,没有真正的目的)。
编辑:如其他响应中所述,第一行实际上将调用复制构造函数。将与分配运算符相关的注释视为与独立分配相关的行为。
这就是说,编译器如何优化代码将有它自己的影响。如果我有一个初始化构造函数调用"="运算符-如果编译器没有进行优化,那么顶行将执行2次跳转,而不是底行中的一次跳转。
现在,对于最常见的情况,编译器将通过这些情况进行优化,并消除这种效率低下的情况。所以实际上,你描述的所有不同的情况都是一样的。如果您想确切地看到正在执行的操作,可以查看对象代码或编译器的程序集输出。