为啥在初始化列表中没有创建额外副本的情况下分配数据?

Posted

技术标签:

【中文标题】为啥在初始化列表中没有创建额外副本的情况下分配数据?【英文标题】:Why does data get assigned without an extra copy being created, in an initialization list?为什么在初始化列表中没有创建额外副本的情况下分配数据? 【发布时间】:2011-07-13 05:30:16 【问题描述】:

Parashift 很好地解释了初始化列表,但没有解释为什么在 ctor 主体中分配之前会创建一个变量的额外副本,但通过初始化列表分配时不会创建额外副本. 我什至遇到过使用 ++i 而不是 i++ 的建议,因为前者避免了在分配之前创建临时 i 。在 ctor 主体中分配的 POD 是否相同?在赋值发生之前创建了一个临时变量?

换句话说,为什么编译器需要创建一个变量的额外副本?为什么不能直接赋值变量?为什么?

【问题讨论】:

对于 ++i 与 i++,现代编译器通常会在可能的情况下将 i++ 优化为等效于 ++i,但不同之处在于 i++ 必须生成一个临时的(原始值) i) 在被评估的语句中使用。因此 i++ 是“后增量”运算符;在获取原始值之前,它不会增加值。我通常使用 ++i ,除非在极少数情况下我愿意使用 i++ 作为更大语句的一部分。在较大的语句中使用变量时修改变量可能会导致一些模糊的错误,这些错误很容易在扫描时遗漏。 您的标题显示了一个根本性的误解。数据在初始化列表中没有分配,它被初始化 @FredOverflow,如果您评论的第二行是作为答案写的,我会选择它作为接受的答案。 【参考方案1】:

考虑以下几点:

struct C  
    C()  /* construct the object */ 
;

struct X 
    C member;

    X()  member = C(); 
;

X() 构造函数与您所说的相同:

X() : member()  member = C(); 

首先,调用C 构造函数来构造member 数据成员。然后X的主体被执行,第二个临时的C对象被创建并分配给member,然后这个临时对象被销毁。


请注意,这仅适用于自动初始化的类型。如果memberint 类型或POD 类类型(作为示例),则在输入X 构造函数的主体时,它将未初始化。

对于此类类型,从性能的角度来看,是在初始化列表中初始化数据成员还是在构造函数的主体中分配给数据成员并不重要;差异完全是风格上的。在可能的情况下,为了保持一致性,仍应首选初始化列表。

【讨论】:

谢谢,但我的问题是“为什么?”。为什么会创建额外的临时 C 对象?为什么不能通过第一次调用C 的构造函数来完成对member 的赋值?此外,这种临时变量创建即使对于 POD 也会发生? "为什么不能通过第一次调用 C 的构造函数来完成对成员的赋值?"如果是这种情况,构造函数中的赋值将是特殊的,并且需要对通常的赋值规则进行例外处理。 C++ 最不需要的就是更多的特殊情况和额外的规则。 好的,我想这是我正在寻找的“分配规则”。我似乎无法弄清楚为什么需要在分配之前制作临时副本。从标准的第 3.10 节中,我所能想到的就是“这就是它在 C++ 中完成的方式”。只有创作者会知道为什么:)【参考方案2】:

专门针对您的第一个关于副本的问题(不涉及“++i”与“i++”):

构造函数是一个函数,它有参数。这些参数是作为参数传递的变量的副本(除非使用引用传递)。您现在可以操作此副本或用它做任何其他事情。然后,当您分配时,您获取参数的这个副本(操纵的可能性)并将其分配给成员变量。当您使用初始化列表时,编译器可以在没有此副本的情况下立即优化分配,因为除了初始化之外您不会将其用于任何其他操作(并且在将其分配给成员变量之前无法对其进行修改)。

【讨论】:

但是,即使当变量作为const 引用传递时,也鼓励使用初始化列表,对吧?就像在复制ctor中一样。我觉得原因是别的。【参考方案3】:

为什么在赋值之前创建一个变量的额外副本 ctor 正文,但通过 初始化列表。

因为赋值遵循初始化。换句话说,赋值是可选的,但初始化是强制性的。您可能不会注意到 POD 有太大的不同。但同样适用于用户定义的数据类型。

使用 ++i 代替 i++ 的建议

同样对于 POD 而言,这并不重要。但是对于用户定义的类,i++ 确实会创建一个临时副本。所以最好使用++i

struct A 
  A operator ++ (int)  // example of i++
  
    A temp = *this;
    this->value ++;
    return temp;    // makes 2 copies "temp" and return value
  
  A& operator ++ ()  // example of ++i
  
    this->value ++;
    return *this;  // no copy
  
;

【讨论】:

并非所有类型都必须进行初始化。 int x; // x is not initialized. @James,一些编译器确实初始化了int。但是,我提到了You may not notice much difference for PODs.

以上是关于为啥在初始化列表中没有创建额外副本的情况下分配数据?的主要内容,如果未能解决你的问题,请参考以下文章

在没有动态分配的情况下在构造时组合对象时避免数据的多个副本

为啥在已删除的默认 ctor 旁边定义一个空的副本 ctor 会使空列表的值初始化失败?

为啥我的 C# 数组在没有分配任何内容的情况下发生变化?

变量的存储空间分配情况

为啥分配为 None 时会创建副本?

指针是不是有可能在没有任何数据副本的情况下由向量拥有?