复制初始化和直接初始化之间的C ++有什么区别?

复制初始化和直接初始化之间的C ++有什么区别?

问题描述:

假设我有这个函数:

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 cite text as proof. Also add other cases please.

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

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

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.5/14.

double b1 = 0.5;
double b2(0.5);

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

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

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

这样做不一样。第一个默认初始化如果 A 是非POD,并且不对POD执行任何初始化(读取 8.5 / 9 )。第二个副本初始化:Value初始化一个临时,然后将该值复制到 c2 (读取 5.2.3 / 2 8.5 / 14 )。这当然需要一个非显式的拷贝构造函数(Read 8.5 / 14 12.3.1 / 3 code> 13.3.1.3/1 )。第三个为函数 c3 创建一个函数声明,它返回一个 A ,函数指针返回一个函数返回a A (阅读 8.2 )。

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

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

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

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:


  • 直接初始化就像对重载函数的函数调用:在这种情况下,函数是 T 包括 explicit ones),参数是 x 。重载分辨率将找到最佳匹配构造函数,并且在需要时将执行任何隐式转换。

  • 复制初始化构造一个隐式转换序列:它尝试将 x 转换为 / code>。 (然后可以将该对象复制到要初始化的对象中,因此也需要复制构造函数 - 但这在下面不是重要的)

  • Direct initialization behaves like a function call to an overloaded function: The functions, in this case, are the constructors of T (including explicit ones), 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 x to 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)

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

As you see, copy initialization is 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 addition can 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 explicit constructors.

#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. 直接初始化

首先不知道有关转换。它只是试图调用一个构造函数。在这种情况下,以下构造函数可用,并且是完全匹配:

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&)

没有转换,更不用说用户定义的转换,需要调用该构造函数这里没有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.

复制初始化

a 未键入 B 或从中派生时(这在这里显然是这种情况)的转换序列。所以它会寻找做转换的方法,并会找到以下候选人

As said above, copy initialization will construct a conversion sequence when a has not type B or 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&);

请注意我如何重写转换函数:参数类型反映 this 指针,在非const成员函数中是非const。现在,我们用 x 作为参数调用这些候选。胜利者是转换函数:因为如果我们有两个候选函数都接受对同一类型的引用,那么 less const 版本胜出(顺便说一下,这也是更喜欢非-const成员函数调用非const对象)。

Notice how I rewrote the conversion function: The parameter type reflects the type of the this pointer, which in a non-const member function is to non-const. Now, we call these candidates with x as 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 const version wins (this is, by the way, also the mechanism that prefers non-const member function calls for non-const objects).

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

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 -pedantic makes it output the proper ambiguity warning too, though.

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

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