12

Here's some code:

class MyClass
{
public:
    int y;
};

int main()
{
    MyClass item1;
    MyClass item2 = MyClass();
}

When I run this, I receive the following values:

item1.y == [garbage]
item2.y == 0

Which, well, surprises me.

I expected item1 to be default-constructed and item2 to be copy-constructed off an anonymous default-constructed instance of MyClass, resulting in both equaling 0 (since default-constructors initialize members to default values). Examining the assembly:

//MyClass item1;
//MyClass item2 = MyClass();
xor         eax,eax  
mov         dword ptr [ebp-128h],eax  
mov         ecx,dword ptr [ebp-128h]  
mov         dword ptr [item2],ecx  

Shows item2 being constructed by writing a '0' value somewhere temporary and then copying it into item2, as expected. However, there's NO assembly for item1.

So, the program has memory for item1 in the stack but it never constructs item1.

I can appreciate wanting that behavior for speed purposes, but I want the best of both worlds! I want to know item1.y == 0 (it gets constructed), but I don't want to waste time on default-construct-anonymous-instance-then-copy-construct like item2 does.

Frustratingly, I can't force a default-construct by saying MyClass item1(); since that is interpreted as a function prototype.

So... if I want to use the default constructor on item1 without also copy-constructing, how the heck do I do that?

Side-note: it looks like if I declare a constructor for MyClass, item1 is constructed as usual. So this behavior only applies to compiler-generated constructors.

4

4 回答 4

16

让你的班级看起来像这样:

class MyClass 
{
public:
    MyClass() : y(0) 
    {
    }

public:
    int y;
};

这两个例子都可以正常工作。您的问题是,如果没有提供构造函数,则不会初始化基本类型成员。y代表任何随机数据恰好位于堆栈中所在位置的位置也是如此item1

显式实现构造函数解决了这个问题。

这种行为的存在是因为 C++ 的“你只需为你使用的东西付费”的心态。基本类型没有“默认值”,因为这会不必要地(稍微)增加分配某些东西的成本,然后再填充它,因为这些值实际上被设置了两次。一次用于“默认值”,一次用于您实际想要的值。

于 2013-10-28T03:03:17.053 回答
10

在 C++ 中,您只需为您要求完成的事情付费。拥有不初始化其数据的类型的能力可能是一个重要的性能提升:令人讨厌的是,这也是默认行为,因为它是从 C 继承的。

您可以通过创建一个零初始化 的构造函数int、使用{}统一初始化语法或int y = 0;在类型声明中使用新的默认语法来解决此问题(最后一个需要 C++11,如果类型为,则第二个需要 C++11非 POD)。

Foo f = Foo();通过检查调试构建程序集,您对性能的担忧会被误导。市场上所有非脑死亡编译器都支持这种微不足道的复制省略,即使 ctor 有副作用也是合法的。

于 2013-10-28T03:07:58.087 回答
6

问题是你对两件事的误解。

  1. 编译器生成的默认构造函数做了什么。
  2. 什么是声明(以及使用赋值时)。

编译器生成的默认构造函数做了什么。

如果您没有定义构造函数,编译器将为您生成默认构造函数。但是有两个不同的版本。一个“值初始化”默认构造函数(对于内置 POD 类型,它什么都不做,并且使它们未初始化)。一个“零初始化”默认构造函数(对于内置 POD 类型将它们设置为零)。

什么是声明(以及使用赋值时)。

赋值运算符仅在 左侧的对象=已完全构造时适用。由于声明中的对象直到 ';' 才完全构造 这不是一项任务。

Bar x = Bar(); // There is no assignment here (this is a declaration using the default constructor
Bar y = Bar(2);// There is no assignment here (this is a declaration using a constructor).

这是使用复制构造函数从临时对象构造对象。但这并不重要,因为编译器只是忽略了实际的副本并在适当的位置构建,所以如果发生任何副本,我会感到非常惊讶。

您的代码中发生了什么

int  x;          // default-Inititalized.      [ Value = Garbage]
int  z = int();  // Zero-Inititalized.         [ Value = 0]

相同的规则适用于具有编译器生成的默认构造函数的类:

LokiClass  xL;               // Value-Initialized -> Default Initialized
                             //                     This is an explicit call to 
                             //                     the default constructor but  
                             //                     will only Value-Initialize
                             //                     class types and not initialize
                             //                     built-in POD types. 
LokiClass  yL = LokiClass(); // Zero-Initialized    This is an explicit call to 
                             //                     the default constructor but  
                             //                     makes sure to use the 
                             //                     Zero-Initialization version if
                             //                     it is the compiler generated 
                             //                     version.

LokiClass  y1L {};           // C++11 version of Zero-Initialization constructor used.

LokiClass  zL((LokiClass()));// This is copy construction
                             // Which will probably lead to copy elision by the compiler.

那么值/零初始化有什么区别呢?
值初始化不会对内置 POD 类型进行初始化。它将在任何基类和成员上调用值初始化编译器生成的默认构造函数(注意,如果您定义了默认构造函数,那么它将使用它,因为没有编译器生成一个)。

零初始化将对 POD 类型进行零初始化。它将在任何基类和成员上调用零初始化编译器生成的默认构造函数(请注意,如果您定义了默认构造函数,那么它将使用它,因为没有编译器生成一个)。

于 2013-10-28T05:11:07.737 回答
2

你可以简单地做:

MyClass item1;

根据变量,它可能会或可能不会将 POD 类型初始化为零。

或者,如果你使用 C++11,你可以这样做:

MyClass item1 {};

显式调用默认构造函数。

item1 没有汇编代码的原因是编译器认为没有必要为所示的类生成代码,因为它没有您编写的显式默认构造函数。

于 2013-10-28T02:49:56.460 回答