问题描述
假设我具有此功能:
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.
推荐答案
C ++ 17更新
在C ++ 17中, A_factory_func()
的含义从创建临时对象(C ++< = 14)更改为仅指定此对象的初始化表达式在C ++ 17中初始化为(宽松地说)。这些对象(称为结果对象)是由声明(如 a1
)创建的变量,是在初始化结束时被丢弃的人造对象,或者如果对象是引用绑定所必需的(例如,在 A_factory_func();
中。在最后一种情况下,是人为创建的对象,称为临时实现,因为 A_factory_func()
没有变量或引用,否则该变量或引用将需要存在一个对象)。
C++17 Update
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).
例如, a1
和 a2
的特殊规则表示,在此类声明中,相同的prvalue初始化程序的结果对象类型为 a1
的变量为 a1
,因此 A_factory_func()
直接初始化对象 a1
。任何中间函数式强制转换都不会起作用,因为 A_factory_func(another-prvalue)
只是通过外部prvalue的结果对象,同时也是结果对象
As examples in our case, in the case of a1
and a2
special rules say that in such declarations, the result object of a prvalue initializer of the same type as a1
is 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.
A a1 = A_factory_func();
A a2(A_factory_func());
取决于 A_factory_func()
返回的类型。我假设它返回 A
-然后执行相同的操作-除了当复制构造函数是显式的时,第一个将失败。阅读
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
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.
A c1;
A c2 = A();
A c3(A());
这是不一样的。第一个默认值初始化-如果 A
是非POD,并且不对POD进行任何初始化(请阅读)。第二个副本初始化:值-初始化临时对象,然后将该值复制到 c2
(请阅读和)。当然,这将需要非明确的副本构造函数(请阅读和和)。第三个为函数 c3
创建一个函数声明,该函数声明返回 A
并接受指向函数返回的函数指针一个 A
(请阅读)。
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.6/9). The second copy initializes: Value-initializes a temporary and then copies that value into c2
(Read 5.2.3/2 and 8.6/14). This of course will require a non-explicit copy constructor (Read 8.6/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
的构造函数(包括显式
),参数为x
。重载解析将找到最匹配的构造函数,并在需要时进行所需的任何隐式转换。 - 复制初始化会构造一个隐式转换序列:它将尝试将
x
转换为T
。 (然后它可能会将那个对象复制到要初始化的对象中,因此也需要一个复制构造函数-但这在下面并不重要)
- Direct initialization behaves like a function call to an overloaded function: The functions, in this case, are the constructors of
T
(includingexplicit
ones), and the argument isx
. 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 typeT
. (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)
如您所见,对于可能的隐式转换,复制初始化在某种程度上是直接初始化的一部分:尽管直接初始化具有所有可调用的构造函数,另外还有 可以执行它需要匹配参数类型的任何隐式转换,复制初始化可以只设置一个隐式转换序列。
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.
我努力了,,而无需通过 explicit
构造函数使用显而易见。
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?
-
直接初始化
它首先对转换一无所知。它将仅尝试调用构造函数。在这种情况下,可以使用以下构造函数,并且是精确匹配:
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&);
注意我如何重写转换函数:参数类型反映了此指针,在非常量成员函数中,该指针指向非常量。现在,我们以 x
作为参数来称呼这些候选者。赢家是转换函数:因为如果我们有两个候选函数都接受对相同类型的引用,那么 less const 版本会获胜(顺便说一句,这也是首选使用non的机制。 -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&
):Comeau编译器会正确拒绝它,但GCC在非non脚模式下接受它。不过,切换到 -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!
这篇关于复制初始化和直接初始化之间有区别吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!