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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1051379/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 18:34:22  来源:igfitidea点击:

Is there a difference between copy initialization and direct initialization?

c++initialization

提问by rlbond

Suppose I have this 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());
}

In each grouping, are these statements identical? Or is there an extra (possibly optimizable) copy in some of the initializations?

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

I have seen people say both things. Please citetext as proof. Also add other cases please.

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

回答by Johannes Schaub - litb

C++17 Update

C++17 更新

In C++17, the meaning of A_factory_func()changed from creating a temporary object (C++<=14) to just specifying the initialization of whatever object this expression is initialized to (loosely speaking) in C++17. These objects (called "result objects") are the variables created by a declaration (like a1), artificial objects created when the initialization ends up being discarded, or if an object is needed for reference binding (like, in A_factory_func();. In the last case, an object is artificially created, called "temporary materialization", because A_factory_func()doesn't have a variable or reference that otherwise would require an object to exist).

在 C++17 中,含义A_factory_func()从创建临时对象 (C++<=14) 变为仅指定在 C++17 中将此表达式初始化为(粗略地说)的任何对象的初始化。这些对象(称为“结果对象”)是由声明创建的变量(如a1)、初始化结束时创建的人工对象或引用绑定需要一个对象(如 in A_factory_func();。在最后一种情况下,一个对象是人为创建的,称为“临时物化”,因为A_factory_func()它没有变量或引用,否则需要对象存在)。

As examples in our case, in the case of a1and a2special rules say that in such declarations, the result object of a prvalue initializer of the same type as a1is variable a1, and therefore A_factory_func()directly initializes the object a1. Any intermediary functional-style cast would not have any effect, because A_factory_func(another-prvalue)just "passes through" the result object of the outer prvalue to be also the result object of the inner prvalue.

作为我们例子中的例子,在a1a2特殊规则的情况下,在这样的声明中,与类型相同的纯右值初始值设定项的结果对象a1是 variable a1,因此A_factory_func()直接初始化 object a1。任何中间函数式类型转换都不会产生任何效果,因为A_factory_func(another-prvalue)只是“传递”了外部纯右值的结果对象,也是内部纯右值的结果对象。



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

Depends on what type A_factory_func()returns. I assume it returns an A- then it's doing the same - except that when the copy constructor is explicit, then the first one will fail. Read 8.6/14

取决于A_factory_func()返回的类型。我假设它返回一个A- 然后它做同样的事情 - 除了当复制构造函数是显式的时,第一个将失败。阅读8.6/14

double b1 = 0.5;
double b2(0.5);

This is doing the same because it's a built-in type (this means not a class type here). Read 8.6/14.

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

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

This is not doing the same. The first default-initializes if Ais a non-POD, and doesn't do any initialization for a POD (Read 8.6/9). The second copy initializes: Value-initializes a temporary and then copies that value into c2(Read 5.2.3/2and 8.6/14). This of course will require a non-explicit copy constructor (Read 8.6/14and 12.3.1/3and 13.3.1.3/1). The third creates a function declaration for a function c3that returns an Aand that takes a function pointer to a function returning a A(Read 8.2).

这不是在做同样的事情。第一个默认初始化 ifA是非 POD,并且不对 POD 进行任何初始化(阅读8.6/9)。第二个副本初始化:值初始化一个临时值,然后将该值复制到c2(读取5.2.3/28.6/14)。这当然需要一个非显式复制构造函数(阅读8.6/1412.3.1/313.3.1.3/1)。第三个函数为c3返回a 的函数创建函数声明A,并使用指向返回 a 的函数的函数指针A(阅读8.2)。



Delving into InitializationsDirect and Copy initialization

深入研究初始化直接和复制初始化

While they look identical and are supposed to do the same, these two forms are remarkably different in certain cases. The two forms of initialization are direct and copy initialization:

虽然它们看起来相同并且应该做同样的事情,但这两种形式在某些情况下却截然不同。初始化的两种形式是直接初始化和复制初始化:

T t(x);
T t = x;

There is behavior we can attribute to each of them:

我们可以将行为归因于它们中的每一个:

  • Direct initialization behaves like a function call to an overloaded function: The functions, in this case, are the constructors of T(including explicitones), and the argument is x. Overload resolution will find the best matching constructor, and when needed will do any implicit conversion required.
  • Copy initialization constructs an implicit conversion sequence: It tries to convert xto an object of type T. (It then may copy over that object into the to-initialized object, so a copy constructor is needed too - but this is not important below)
  • 直接初始化的行为就像对重载函数的函数调用:在这种情况下,函数是T(包括函数explicit)的构造函数,参数是x。重载解析将找到最匹配的构造函数,并在需要时进行任何所需的隐式转换。
  • 复制初始化构造了一个隐式转换序列:它尝试转换x为类型为 的对象T。(然后它可能会将该对象复制到要初始化的对象中,因此也需要一个复制构造函数 - 但这在下面并不重要)

As you see, copy initializationis in some way a part of direct initialization with regard to possible implicit conversions: While direct initialization has all constructors available to call, and in additioncan do any implicit conversion it needs to match up argument types, copy initialization can just set up one implicit conversion sequence.

如您所见,复制初始化在某种程度上是关于可能的隐式转换的直接初始化的一部分:虽然直接初始化具有可调用的所有构造函数,此外还可以进行任何需要匹配参数类型的隐式转换,复制初始化可以只设置一个隐式转换序列。

I tried hard and got the following code to output different text for each of those forms, without using the "obvious" through explicitconstructors.

我努力尝试并使用以下代码为每个表单输出不同的文本,而没有通过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>

How does it work, and why does it output that result?

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

  1. Direct initialization

    It first doesn't know anything about conversion. It will just try to call a constructor. In this case, the following constructor is available and is an exact match:

    B(A const&)
    

    There is no conversion, much less a user defined conversion, needed to call that constructor (note that no const qualification conversion happens here either). And so direct initialization will call it.

  2. Copy initialization

    As said above, copy initialization will construct a conversion sequence when ahas not type Bor derived from it (which is clearly the case here). So it will look for ways to do the conversion, and will find the following candidates

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

    Notice how I rewrote the conversion function: The parameter type reflects the type of the thispointer, which in a non-const member function is to non-const. Now, we call these candidates with xas argument. The winner is the conversion function: Because if we have two candidate functions both accepting a reference to the same type, then the less constversion wins (this is, by the way, also the mechanism that prefers non-const member function calls for non-const objects).

    Note that if we change the conversion function to be a const member function, then the conversion is ambiguous (because both have a parameter type of A const&then): The Comeau compiler rejects it properly, but GCC accepts it in non-pedantic mode. Switching to -pedanticmakes it output the proper ambiguity warning too, though.

  1. 直接初始化

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

    B(A const&)
    

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

  2. 复制初始化

    如上所述,当a没有类型B或派生自它时,复制初始化将构造一个转换序列(这里显然是这种情况)。所以它会寻找进行转换的方法,并会找到以下候选者

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

    注意我是如何重写转换函数的:参数类型反映了this指针的类型,在非常量成员函数中它是非常量的。现在,我们将这些候选人x称为 as 参数。赢家是转换函数:因为如果我们有两个候选函数都接受对同一类型的引用,那么较少的 const版本获胜(顺便说一下,这也是喜欢非常量成员函数调用非的机制-const 对象)。

    请注意,如果我们将转换函数更改为 const 成员函数,则转换是不明确的(因为两者都有一个A const&then参数类型):Comeau 编译器正确拒绝了它,但 GCC 在非迂腐模式下接受了它。不过,切换到-pedantic使其也输出正确的歧义警告。

I hope this helps somewhat to make it clearer how these two forms differ!

我希望这有助于更清楚地说明这两种形式的不同之处!

回答by Mehrdad Afshari

Assignmentis different from initialization.

赋值不同于初始化

Both of the following lines do initialization. A single constructor call is done:

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

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

but it's not equivalent to:

但它不等同于:

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

I don't have a text at the moment to prove this but it's very easy to experiment:

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

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

回答by Kirill V. Lyadvinsky

double b1 = 0.5;is implicit call of constructor.

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

double b2(0.5);is explicit call.

double b2(0.5);是显式调用。

Look at the following code to see the difference:

查看以下代码以了解区别:

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

If your class has no explicit constuctors than explicit and implicit calls are identical.

如果您的类没有显式构造函数,则显式和隐式调用是相同的。

回答by CB Bailey

First grouping: it depends on what A_factory_funcreturns. The first line is an example of copy initialization, the second line is direct initialization. If A_factory_funcreturns an Aobject then they are equivalent, they both call the copy constructor for A, otherwise the first version creates an rvalue of type Afrom an available conversion operators for the return type of A_factory_funcor appropriate Aconstructors, and then calls the copy constructor to construct a1from this temporary. The second version attempts to find a suitable constructor that takes whatever A_factory_funcreturns, or that takes something that the return value can be implicitly converted to.

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

Second grouping: exactly the same logic holds, except that built in types don't have any exotic constructors so they are, in practice, identical.

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

Third grouping: c1is default initialized, c2is copy-initialized from a value initialized temporary. Any members of c1that have pod-type (or members of members, etc., etc.) may not be initialized if the user supplied default constructors (if any) do not explicitly initialize them. For c2, it depends on whether there is a user supplied copy constructor and whether that appropriately initializes those members, but the members of the temporary will all be initialized (zero-initialized if not otherwise explicitly initialized). As litb spotted, c3is a trap. It's actually a function declaration.

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

回答by John H.

Of note:

值得注意的是:

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

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

I.e., for copy-initialization.

即,用于复制初始化。

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

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

In other words, a good compiler will notcreate a copy for copy-initialization when it can be avoided; instead it will just call the constructor directly -- ie, just like for direct-initialization.

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

In other words, copy-initialization is just like direct-initialization in most cases <opinion> where understandable code has been written. Since direct-initialization potentially causes arbitrary (and therefore probably unknown) conversions, I prefer to always use copy-initialization when possible. (With the bonus that it actually looks like initialization.)</opinion>

换句话说,在大多数情况下,复制初始化就像直接初始化一样,<opinion> 已经编写了可理解的代码。由于直接初始化可能会导致任意(因此可能是未知的)转换,我更喜欢尽可能使用复制初始化。(额外的好处是它实际上看起来像初始化。)</opinion>

Technical goriness: [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.

技术细节:[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.

Glad I'm not writing a C++ compiler.

很高兴我没有编写 C++ 编译器。

回答by bashrc

Answering with respect to this part:

关于这部分的回答:

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

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

Since most of the answers are pre-c++11 I am adding what c++11 has to say about this:

由于大多数答案都在 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.

简单类型说明符 (7.1.6.2) 或类型名称说明符 (14.6) 后跟括号表达式列表构造给定表达式列表的指定类型的值。如果表达式列表是单个表达式,则类型转换表达式与相应的强制转换表达式 (5.4) 等效(在定义上,如果在含义上定义)。如果指定的类型是类类型,则类类型应该是完整的。如果表达式列表指定了多个值,则该类型应为具有适当声明的构造函数 (8.5, 12.1) 的类,并且表达式 T(x1, x2, ...) 等效于声明 T t (x1, x2, ...); 对于一些发明的临时变量 t,结果是 t 的值作为纯右值。

So optimization or not they are equivalent as per the standard. Note that this is in accordance with what other answers have mentioned. Just quoting what the standard has to say for sake of correctness.

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

回答by BattleTested

You can see its difference in explicitand implicitconstructor types when you initialize an object :

当你初始化一个对象时,你可以看到它explicitimplicit构造函数类型的区别:

Classes :

课程:

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

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

And in themainfunction :

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
}

By default, a constructor is as implicitso you have two way to initialize it :

默认情况下,构造函数是implicit这样你有两种方法来初始化它:

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

And by defining a structure as explicitjust you have one way as direct :

通过定义一个结构,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

回答by Bharat

This is from C++ Programming Language by Bjarne Stroustrup:

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

An initialization with an = is considered a copy initialization. In principle, a copy of the initializer (the object we are copying from) is placed into the initialized object. However, such a copy may be optimized away (elided), and a move operation (based on move semantics) may be used if the initializer is an rvalue. Leaving out the = makes the initialization explicit. Explicit initialization is known as direct initialization.

带有 = 的初始化被认为是复制初始化。原则上,初始化器的副本(我们从中复制的对象)被放置到初始化对象中。但是,这样的副本可能会被优化掉(省略),并且如果初始化程序是右值,则可以使用移动操作(基于移动语义)。省略 = 使初始化显式。显式初始化称为直接初始化

回答by dborba

A lot of these cases are subject to an object's implementation so it's hard to give you a concrete answer.

很多这些情况都取决于对象的实现,所以很难给你一个具体的答案。

Consider the case

考虑情况

A a = 5;
A a(5);

In this case assuming a proper assignment operator & initializing constructor which accept a single integer argument, how I implement said methods affects the behavior of each line. It is common practice however for one of those to call the other in the implementation as to eliminate duplicate code (although in a case as simple as this there would be no real purpose.)

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

Edit: As mentioned in other responses, the first line will in fact call the copy constructor. Consider the comments relating to the assignment operator as behavior pertaining to a stand alone assignment.

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

That said, how the compiler optimizes the code will then have it's own impact. If I have the initializing constructor calling the "=" operator - if the compiler makes no optimizations, the top line would then perform 2 jumps as opposed to one in the bottom line.

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

Now, for the most common situations, your compiler will optimize through these cases and eliminate this type of inefficiencies. So effectively all the different situations you describe will turn out the same. If you want to see exactly what is being done, you can look at the object code or an assembly output of your compiler.

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