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

时间:2012-06-27 09:37:16

标签: c++ initialization history language-design

Why is copy constructor called instead of conversion constructor?

有些相关

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

A a(b);
A a = b;

我想知道他们有不同定义行为的动机。对于复制初始化,涉及额外的副本,我不能想到该副本的任何目的。由于它是来自临时文件的副本,它可以并且可能会被优化,因此用户不能依赖它发生 - 因为额外的副本本身不足以应对不同的行为。那么......为什么?

4 个答案:

答案 0 :(得分:4)

  

由于它是来自临时的副本,因此可以 优化

此处的关键字可能是。该标准允许但不要求编译器优化副本。如果某些编译器允许此代码(已优化),但其他编译器拒绝它(非优化),则这将非常不一致。

因此标准规定了一种一致的处理方法 - 每个人都必须检查复制构造函数是否可访问,无论他们是否使用它。

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


另一个例子,考虑

A a;
B b;

A a1 = a;
A a2 = b;

允许a2同样不一致,但当a1的复制构造函数为私有时,禁止A


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

  

如果初始化是直接初始化,或者它是复制初始化,其中源类型的cv-nonqualified版本与目标类相同,或者是派生类,则考虑构造函数。列举了适用的构造函数(13.3.1.3),并通过重载解析(13.3)选择最佳构造函数。调用所选的构造函数以初始化对象,初始化表达式或表达式列表作为其参数。如果没有构造函数适用,或者重载决策不明确,则初始化是不正确的。

     

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

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

答案 1 :(得分:4)

只是一种推测,但是如果没有Bjarne Stroustrup确认它是如何确实的话,我恐怕很难确定:

它是以这种方式设计的,因为假设程序员会期望这样的行为,他会期望在使用=符号时完成复制,而不是使用直接初始化器语法。

我认为可能的复制省略仅在标准的后续版本中添加,但我不确定 - 这是某人可以通过检查标准历史来确定的。

答案 2 :(得分:1)

采用以下示例:

struct X
{
    X(int);
    X(const X&);
};

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

在我测试的编译器中,即使启用了完全优化,foo的参数也会被复制。由此,我们可以收集副本不会/不得在所有情况下被删除。

现在让我们从语言设计的角度思考,想象一下如果你想在需要副本的时候制定规则,那么你必须考虑所有的场景。这将非常困难。而且,即使你能够提出规则,它们也会非常复杂,几乎不可能让人理解。但是,与此同时,如果你强制复制到处,那将是非常低效的。这就是为什么规则就是这样的原因,你让规则易于理解,让人们理解,同时如果可以避免的话,仍然不会强制复制。

我现在必须承认,这个答案与Suma的答案非常相似。我们的想法是,您可以期待当前规则的行为,而其他任何事情都会让人们难以理解。

答案 3 :(得分:0)

内置类型的初始化,如:

int i = 2;

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

int i(2);
即使一些数学家可能会争论这一点。毕竟,在调用函数(在这种情况下是构造函数)并向其传递参数时,没有什么不自然的。

对于内置类型,这两种类型的初始化是相同的。在前一种情况下没有额外的副本。 这就是两种类型的初始化的原因,最初没有特定的意图使它们的行为不同。

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

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

您可能拥有额外副本并且可能被省略的事实是对用户定义类型的优化。复制省略和显式构造函数都迟到了语言。标准允许在一段时间使用后进行优化,这并不奇怪。此外,现在您可以从重载决策候选中删除显式构造函数。