复制和直接初始化背后的动机有什么不同?

为什么复制构造函数调用而不是转换构造函数有点相关?

有两种初始化语法,直接和复制初始化:

A a(b); A a = b; 

我想知道他们有不同的定义行为的动机。 对于复制初始化,涉及额外的副本,我不能想到该副本的任何目的。 由于它是一个临时副本,因此它可能也可能会被优化,所以用户不能依赖它发生 – 但是额外的副本本身对于不同的行为是不够的。 所以为什么?

只有一个猜测,但是如果没有Bjarne Stroustrup证实它真的是这样的话,我恐怕很难确定:

它是这样devise的,因为假定程序员期望这样的行为,他会期望复制在使用= sign时完成,而不是用直接初始化语法完成。

我认为可能的复制elision只是在标准的后续版本中添加的,但我不确定 – 通过检查标准历史logging,某些人可能可以肯定地知道这一点。

由于它是一个临时副本,所以可能也可能会进行优化

这里的关键字可能是 。 该标准允许但不要求编译器优化拷贝。 如果一些编译器允许这个代码(优化),但其他编译器拒绝它(非优化),这将是非常不一致的。

所以标准规定了处理这个问题的一致的方法 – 每个人都必须检查拷贝构造函数是否可访问,不pipe他们是否使用它。

这个想法是,所有编译器都应该接受代码或拒绝它。 否则,它将是不可移植的。


再比如,考虑一下

 A a; B b; A a1 = a; A a2 = b; 

A的拷贝构造函数是私有的时候,允许a2但是禁止a1同样是不一致的。


我们还可以从标准文本中看到,初始化类对象的两种方法意图不同(8.5 / 16):

如果初始化是直接初始化,或者如果它是复制初始化,其中源types的cv不合格版本是目标类的类或派生类,则考虑构造函数。 枚举适用的构造函数(13.3.1.3),通过重载parsing(13.3)select最好的构造函数。 调用如此select的构造函数以初始化器expression式或expression式列表作为参数来初始化对象。 如果不应用构造函数,或者重载parsing模糊不清,则初始化不合格。

否则(即,对于其余的复制初始化情况),可以从源types转换为目标types或者(当使用转换函数时)转换为其派生类的用户定义的转换序列被枚举,如13.3所述。 1.4,最好的是通过重载决议(13.3)来select。 如果转换不能完成或不明确,则初始化格式不正确。 所选函数以初始值expression式作为参数来调用; 如果该函数是一个构造函数,则该调用将初始化目标types的cv不合格版本的临时值。 临时是一个prvalue。 调用的结果(这是构造函数的临时情况)用于根据上述规则直接初始化作为复制初始化目标的对象。 在某些情况下,允许实现通过将中间结果直接构造到被初始化的对象中来消除该直接初始化中固有的复制; 见12.2,12.8。

不同之处在于直接初始化直接使用构造类的构造函数。 使用复制初始化时,会考虑其他转换函数,这些函数可能会产生需要复制的临时文件。

以下面的例子:

 struct X { X(int); X(const X&); }; int foo(X x){/*Do stuff*/ return 1; } X x(1); foo(x); 

在我testing的编译器中, foo的参数总是被复制,即使在完全优化的情况下也是如此。 由此可以看出,在任何情况下,副本都不会被排除。

现在让我们从语言devise的angular度思考,想象一下,如果您想要制定何时需要复制以及何时复制的规则,您就必须考虑所有情况。 这将是非常困难的。 而且,即使你能够提出规则,他们也会非常复杂,几乎不可能让人理解。 但是,与此同时,如果你在任何地方都被迫复制,那将是非常低效的。 这就是为什么规则是这样的,你可以让规则理解人们的理解,同时如果可以避免的话,也不要强制拷贝。

我现在不得不承认,这个答案跟Suma的答案非常相似。 这个想法是,你可以期待现行规则的行为,而其他任何事情对于人们来说都太难了。

内置types的初始化如下所示:

 int i = 2; 

是非常自然的语法,部分原因是由于历史原因(记住你的高中math)。 这比以下更自然:

 int i(2); 

即使有些math家可能会争论这一点。 毕竟,调用一个函数(在这种情况下是一个构造函数)并传递一个参数并不是不自然的。

对于内置types,这两种types的初始化是相同的。 前一种情况没有额外的副本。 这就是有两种types的初始化的原因,原本没有具体的意图,使他们的行为不同。

但是,有用户定义的types,并且语言的一个既定目标是允许它们尽可能地作为内置types。

因此,复制构造(例如,从一些转换函数中获取input)是第一个语法的自然实现。

事实上,你可能有额外的副本,他们可能会被淘汰是对用户定义types的优化。 复制elision和显式构造函数都来到这个语言后面。 标准允许在一定的使用期限后进行优化也就不足为奇了。 此外,现在你可以从重载决议候选人中消除显式的构造函数。