注意: [12.2/1] x一个月一个月 即用于复制初始化。 [12.8/15] x一米一英寸 换句话说,一个好的编译器在可以避免复制初始化的时候,不会为复制初始化创建一个副本;相反,它将直接调用构造函数--也就是说,就像直接初始化一样。 In other words, copy-initialization is just like direct-initialization in most cases 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.) 技术精湛:[12.2/1接上表] 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++编译器。
9条答案
按热度按时间f3temu5u1#
C++17更新
在C17中,
A_factory_func()
的含义从创建临时对象更改为(C〈=14)设置为只指定此表达式初始化到的任何对象的初始化(不严格地说)在C++17中。这些对象(称为“结果对象”)是由声明创建的变量(如a1
),当初始化最终被放弃时创建的人工对象,或者如果引用绑定需要对象(例如,在A_factory_func();
中。在最后一种情况下,对象是人工创建的,称为“临时物化”,因为A_factory_func()
没有变量或引用,否则需要对象存在)。作为我们的例子,在
a1
和a2
的情况下,特殊规则说在这样的声明中,与a1
相同类型的纯右值初始化器的结果对象是变量a1
,因此A_factory_func()
直接初始化对象a1
。因为A_factory_func(another-prvalue)
正好“穿过”外右值的结果对象,从而也成为内右值的结果对象。取决于
A_factory_func()
返回什么类型。我假设它返回一个A
-那么它做的是一样的-除了当复制构造函数是显式的,那么第一个将失败。Read 8.6/14这是做同样的事情,因为它是一个内置类型(这意味着这里不是类类型)。阅读8.6/14。
这是不一样的。如果
A
是非POD,第一个默认初始化,并且不对POD进行任何初始化(阅读8.6/9)。第二个副本初始化:Value-初始化一个临时变量,然后将该值复制到c2
(阅读5.2.3/2和8.6/14),当然这需要一个非显式的复制构造函数(阅读8.6/14和12.3.1/3以及13.3.1.3/1)。第三个代码为函数c3
创建函数声明,该函数返回A
,并接受指向返回A
的函数的函数指针(Read 8.2)。深入了解初始化直接和拷贝初始化
虽然它们看起来完全相同,并且应该做同样的事情,但在某些情况下,这两种形式有很大的不同。这两种形式的初始化是直接初始化和复制初始化:
我们可以将这些行为归因于它们中的每一个:
T
(包括explicit
)的构造函数,参数是x
。重载解析将找到最匹配的构造函数,并在需要时执行所需的任何隐式转换。x
转换为T
类型的对象。(然后它可能会将该对象复制到要初始化的对象中,因此也需要一个复制构造函数-但这在下面并不重要)如您所见,copy initialization 在某种程度上是直接初始化的一部分,与可能的隐式转换有关:直接初始化有所有的构造函数可供调用,而且 * 此外 * 还可以做任何它需要匹配参数类型的隐式转换,而复制初始化只能设置一个隐式转换序列。
我努力尝试了got the following code to output different text for each of those forms,没有使用“显而易见”的
explicit
构造函数。它是如何工作的,为什么输出这样的结果?
1.直接初始化
首先,它不知道任何关于转换的事情,它只会尝试调用一个构造函数,在这个例子中,下面的构造函数是可用的,并且是一个 * 精确匹配 *:
调用构造函数不需要转换,更不用说用户定义的转换了(注意这里也没有发生常量限定转换),所以直接初始化将调用它。
1.复制初始化
如上所述,当
a
没有类型B
或从B
派生时(这里的情况很明显),复制初始化将构造转换序列,因此它将寻找进行转换的方法,并将找到以下候选项注意我是如何重写转换函数的:参数类型反映了
this
指针的类型,在非常数成员函数中是指向非常数的。现在,我们用x
作为参数调用这些候选对象。赢家的是转换函数:因为如果我们有两个候选函数都接受对同一类型的引用,那么 less const 版本会胜出(顺便说一下,这也是一种机制,它更喜欢非常数成员函数调用非常数对象)。注意,如果我们将转换函数更改为const成员函数,则转换是不明确的(因为两者都有
A const&
的参数类型):Comeau编译器正确地拒绝了它,但GCC以非学究式的方式接受了它,不过切换到-pedantic
也会输出正确的歧义警告。cbeh67ev2#
分配与初始化不同。
下面两行都执行 * 初始化 *,只执行一次构造函数调用:
但它不等于
目前我还没有一个文本来证明这一点,但这很容易实验:
ldxq2e6h3#
double b1 = 0.5;
是构造函数的隐式调用。double b2(0.5);
是显式调用。查看下面的代码以了解差异:
如果类没有显式构造函数,那么显式调用和隐式调用是相同的。
pengsaosao4#
当你初始化一个对象时,你可以看到它在
explicit
和implicit
构造函数类型上的区别:课程:
在
main
函数中:默认情况下,构造函数为
implicit
,因此有两种方法初始化它:通过将结构定义为
explicit
,你就有了一种直接的方法:baubqpgj5#
注意:
[12.2/1] x一个月一个月
即用于复制初始化。
[12.8/15] x一米一英寸
换句话说,一个好的编译器在可以避免复制初始化的时候,不会为复制初始化创建一个副本;相反,它将直接调用构造函数--也就是说,就像直接初始化一样。
In other words, copy-initialization is just like direct-initialization in most cases 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.)
技术精湛:[12.2/1接上表]
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++编译器。
byqmnocz6#
第一组:这取决于
A_factory_func
返回什么。第一行是 * 复制初始化 * 的示例,第二行是 * 直接初始化 *。如果A_factory_func
返回A
对象,则它们是等效的,它们都调用A
的复制构造函数,否则第一版本从用于A_factory_func
的返回类型的可用转换运算符或适当的A
构造函数创建A
类型的右值,然后调用复制构造函数从这个临时值构造a1
,第二个版本试图找到一个合适的构造函数,它接受A_factory_func
返回的任何内容,或者接受返回值可以隐式转换成的内容。第二组:完全相同的逻辑成立,除了内置类型没有任何外来的构造函数,因此它们实际上是相同的。
第三组:
c1
是默认初始化的,c2
是从临时初始化的值复制初始化的。c1
的任何具有pod类型的成员(或成员的成员等)可能无法初始化,如果用户提供了默认构造函数(如果有的话)不显式初始化它们。对于c2
,这取决于是否有用户提供的复制构造函数,以及它是否适当地初始化了这些成员,但临时变量的成员都将被初始化(如果没有显式初始化,则初始化为零)正如litb所指出的,c3
是一个陷阱,它实际上是一个函数声明。jtoj6r0c7#
关于本部分的回答:
A c2 = A(); A c3(A());
由于大多数答案都是c11之前的版本,我在这里补充一下c11对这个问题的看法:
简单类型说明符(7.1.6.2)或类型名称说明符公式14.6后面跟着一个括号中的表达式列表构造一个给定类型的值。如果表达式列表是一个单一的表达式,那么类型转换表达式是等价的(在定义中,如果在含义中定义)转换为相应的强制转换表达式(5.4)如果指定的类型是类类型,则类类型应该是完整的。**如果表达式列表指定了多个值,则类型应该是具有适当声明的构造函数的类(8.5,12.1),并且表达式T(x1,x2,...)在效果上等效于声明T t(x1,x2,...);**对于某个虚构的临时变量t,其结果是t的值作为纯右值。
因此,优化与否,根据标准是等效的。注意,这是根据其他答案所提到的。只是引用标准所要说的话,以确保正确性。
tp5buhyn8#
这是来自Bjarne Stroustrup的C++编程语言:
带有=的初始化被认为是 * 复制初始化 *。原则上,初始化式的副本(我们要从中复制的对象)被放入初始化对象中。但是,这样的副本可能会被优化掉(省略),和移动操作(基于移动语义)如果初始化式是一个右值,则可以使用。省略=会使初始化显式。显式初始化称为 * 直接初始化 *。
xqkwcwgp9#
许多这样的情况都取决于对象的实现,因此很难给予具体的答案。
考虑一下这个案例
在这种情况下,假设一个正确的赋值操作符和初始化构造函数接受一个整型参数,我如何实现上述方法会影响每一行的行为。然而,常见的做法是其中一个在实现中调用另一个,以消除重复代码(尽管在如此简单的情况下,没有真实的的目的)。
编辑:正如在其他回复中提到的,第一行实际上会调用复制构造函数。将与赋值运算符相关的注解视为独立赋值的行为。
也就是说,编译器如何优化代码会有自己的影响,如果我让初始化构造函数调用“=”操作符--如果编译器没有进行优化,那么顶行将执行2次跳转,而底行执行1次跳转。
现在,对于最常见的情况,你的编译器将通过这些情况进行优化,并消除这种类型的低效率。所以有效地,你描述的所有不同的情况将变成相同的。如果你想确切地看到正在做什么,你可以看看你的编译器的目标代码或汇编输出。