复制初始化和直接初始化之间有区别吗?

时间:2009-06-26 21:36:19

标签: c++ initialization

假设我有这个功能:

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());
}

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

我见过有人说过这两件事。请引用文字作为证明。还请添加其他案例。

9 个答案:

答案 0 :(得分:236)

答案 1 :(得分:44)

分配初始化不同。

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

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 =

我目前没有文字来证明这一点,但实验起来很容易:

#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;
}

答案 2 :(得分:16)

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;
}

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

答案 3 :(得分:5)

值得注意的是:

[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 ...

换句话说,一个好的编译器将为可以避免的复制初始化创建一个副本;相反,它只是直接调用构造函数 - 即,就像直接初始化一样。

换句话说,复制初始化就像大多数情况下的直接初始化一样&lt; views&gt;编写可理解代码的地方。由于直接初始化可能会导致任意(因此可能是未知的)转换,因此我倾向于在可能的情况下始终使用复制初始化。 (实际上看起来像初始化的奖励。)&lt; / opinion&gt;

技术性: [12.2 / 1 cont from above] 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 ++编译器。

答案 4 :(得分:4)

首先分组:它取决于A_factory_func返回的内容。第一行是复制初始化的示例,第二行是直接初始化。如果A_factory_func返回A对象,则它们是等效的,它们都会调用A的复制构造函数,否则第一个版本会从可用转换中创建一个类型为A的右值返回类型为A_factory_func或适当的A构造函数的运算符,然后调用复制构造函数从此临时构造a1。第二个版本试图找到一个合适的构造函数,该构造函数接受任何A_factory_func返回,或者返回值可以隐式转换为。

第二个分组:完全相同的逻辑保持不变,除了内置类型没有任何奇异的构造函数,因此它们在实践中是相同的。

第三个分组:c1是默认初始化的,c2是从初始化的值临时复制初始化的。如果用户提供的默认构造函数(如果有的话)没有显式初始化它们,则任何具有pod类型(或成员成员等)的c1成员都可能无法初始化。对于c2,它取决于是否存在用户提供的拷贝构造函数以及是否适当地初始化这些成员,但是临时的成员都将被初始化(如果没有以其他方式显式初始化则为零初始化)。由于被发现,c3是一个陷阱。它实际上是一个函数声明。

答案 5 :(得分:4)

对这部分的回答:

  

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

由于大部分答案都是pre-c ++ 11,我正在补充c ++ 11对此的评价:

  

简单类型说明符(7.1.6.2)或typename-specifier(14.6)   后面跟一个带括号的表达式列表构造一个值   给定表达式列表的指定类型。如果表达式列表是a   单表达式,类型转换表达式是等价的(in   定义,如果在意义上定义)到相应的演员   表达式(5.4)。如果指定的类型是类类型,则为类   类型应完整。 如果表达式列表指定多于a   单值,类型应为具有适当声明的类   构造函数(8.5,12.1),表达式T(x1,x2,...)是   等效于声明T t(x1,x2,...);   发明了临时变量t,结果是t的值   prvalue。

所以优化与否,它们与标准相同。 请注意,这与其他答案提到的一致。为了正确起见,只是引用标准所说的内容。

答案 6 :(得分:1)

很多这些案例都受到对象实施的影响,因此很难给出具体答案。

考虑案例

A a = 5;
A a(5);

在这种情况下,假设一个正确的赋值运算符&amp;初始化接受单个整数参数的构造函数,我如何实现所述方法会影响每一行的行为。然而,通常的做法是其中一个人在实现中调用另一个来消除重复的代码(尽管在这种情况下很简单,没有真正的目的。)

编辑:如其他回复中所述,第一行实际上将调用复制构造函数。将与赋值运算符相关的注释视为与独立赋值相关的行为。

那就是说,编译器如何优化代码将会产生自己的影响。如果我有初始化构造函数调用“=”运算符 - 如果编译器没有进行优化,那么顶行将执行2次跳转而不是底线的跳转。

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

答案 7 :(得分:1)

初始化对象时,您可以在explicitimplicit构造函数类型中看到它的区别:

课程:

class A
{
    A(int) { }      // converting constructor
    A(int, int) { } // converting constructor (C++11)
};

class B
{
    explicit B(int) { }
    explicit B(int, int) { }
};

main 函数中

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
}

默认情况下,构造函数为implicit,因此您有两种方法来初始化它:

A a1 = 1;        // this is copy initialization
A a2(2);         // this is direct initialization

通过将结构定义为explicit,您可以直接使用一种方式:

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

答案 8 :(得分:0)

这是来自Bjarne Stroustrup的C ++编程语言:

带有=的初始化被视为复制初始化。原则上,将初始化程序的副本(我们要从中复制的对象)放入初始化的对象中。但是,可以优化(删除)此类副本,并且如果初始化程序是右值,则可以使用移动操作(基于移动语义)。省略=使初始化显式。显式初始化称为直接初始化