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.

Ben Walker
  • 908
  • 2
  • 8
  • 16
  • 1
    Declare and implement a constructor without arguments, it will be always called and do what you expect it for. – Pierre Emmanuel Lallemant Oct 28 '13 at 02:52
  • Yeah, you're right -- this weird behavior goes away if there's an explicit constructor, even if it's just MyClass() {}; (which makes item2.y == [garbage]. Why does this behavior exist, then? – Ben Walker Oct 28 '13 at 03:01
  • 8
    Why are you talking about performance of debug build assembly? – Yakk - Adam Nevraumont Oct 28 '13 at 03:02
  • 2
    @Yakk: he looks what the code do. – Pierre Emmanuel Lallemant Oct 28 '13 at 03:02
  • A release build will optimise away the "slow" copy constructor, I would bet. – Ken Y-N Oct 28 '13 at 03:07
  • @BenWalker: if you declare MyClass tab[10000]; setting all the attributes of the elements of tab to 0 will take time. In that case, it's justified: you don't ask the compiler to force the initialization of 10000 MyClass instances. – Pierre Emmanuel Lallemant Oct 28 '13 at 03:07
  • possible duplicate of [Does the default constructor initialize built-in types](http://stackoverflow.com/questions/2417065/does-the-default-constructor-initialize-built-in-types) – Ken Y-N Oct 28 '13 at 03:07
  • @Pierre if you think so, you misunderstand C++. The keyword is elision. – Yakk - Adam Nevraumont Oct 28 '13 at 03:20
  • **since default-constructors initialize members to default values**. Not quite correct. There are two version of the default constructor. A "Value-Initialization" and a "Zero-Initialization" version. See http://stackoverflow.com/a/1810320/14065 – Martin York Oct 28 '13 at 05:01
  • @BenWalker: To answer your comment on why this behavior exists. Its to allow optimal code generation. If you want to force zero initialization you can. If you don't want to waste an instruction on initialization you can do that as well without having to jump through any hoops. See: http://stackoverflow.com/a/1910992/14065 – Martin York Oct 28 '13 at 05:38

4 Answers4

16

Make your class look like this:

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

public:
    int y;
};

and both examples will work fine. Your problem is that if no constructor is supplied, the basic type members will not be initialized. So y is representing whatever random data happens to be on the stack in the spot where item1 is residing.

Explicitly implementing a constructor addresses this.

The behavior exists because of C++'s "you only pay for what you use" mentality. Basic types don't have a "default value" as that would make it unnecessarily (slightly) more costly to allocate something and then fill it in later due to the values effectively being set twice. Once for the "default value", once for the value you actually wanted.

Inverse
  • 4,408
  • 2
  • 26
  • 35
Evan Teran
  • 87,561
  • 32
  • 179
  • 238
  • That makes sense! How did it end up that item2.y == 0, though? The anonymous MyClass instance that item2 copy-constructed from should also have a garbage 'y' value. – Ben Walker Oct 28 '13 at 03:15
  • 1
    When you did `item2 = MyClass()` you explicitly ran the constructor for the anonymous object (which default initialized the `int` member, this is called "value-initialization) and then copied that to your item. `item1` simply had no constructor run at all because none was specified. – Evan Teran Oct 28 '13 at 03:26
  • 2
    `item1` had a constructor called - the default constructor. The default constructor in that class doesn't value-initialize members, though, it default-initializes them. – chris Oct 28 '13 at 03:31
  • @chris, I suppose your are right. But it's a POD type, so the default constructor does nothing. – Evan Teran Oct 28 '13 at 03:32
  • The first version uses the compiler generated "Value-Initialized" default constructor. The second one uses the compiler generated "Zero-Initialized" default constructor. Thus `item1.y` is undefined and `item2.y` is zero. See: http://stackoverflow.com/a/1810320/14065 – Martin York Oct 28 '13 at 04:59
  • 2
    So yes an explicit constructor is one solution to the problem, but it is not required. Also by defining a default constructor you are robbing yourself of an optimization opportunity (no initialization (though obviously you want to be careful with that)). By explicitly using the 'Zero-Initialization' compiler generated default constructor you get the same affect. – Martin York Oct 28 '13 at 05:58
  • @BenWalker: Even without the default initialization, `item2.y == 0` shouldn't be surprising. `0` is as valid a garbage result as anything else. – Keith Thompson Oct 28 '13 at 06:34
  • @KeithThompson: `item2.y == 0` is the correct result. This item is explicitly initialized to zero. Unlike `item1.y` which does have `garbage`. – Martin York Oct 28 '13 at 06:48
  • @LokiAstari: OK, but my point is that `0` is not a surprising value even for a garbage value. – Keith Thompson Oct 28 '13 at 07:04
10

In C++ you only pay for what you ask to be done. The ability to have types that do not initialize their data can be an important performance boost: annoyingly, this is also the default behavior, because it was inherited from C.

You can fix this by creating a constructor that zero initializes the int, by using {} uniform initialization syntax, or by using the new default syntax int y = 0; in the type declaration (the last requires C++11, the second requires C++11 if the type is non-POD).

Your performance concerns about Foo f = Foo(); are mislead by examining debug build assembly. Copy elision in such a trivial case is supported by every non brain dead compiler on the market, and is legal even if the ctor has side effects.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
6

The trouble is your misinterpretation of two things.

  1. What the compiler generated default constructor does.
  2. What is a declaration is (and thus when assignment is used).

What the compiler generated default constructor does.

If you do not define a constructor the compiler will generate a default constructor for you. But there are two different version. A 'Value-Initialization' default constructor (which for built-in POD types does nothing and leaves them uninitialized). A 'Zero-Initialization' default constructor (which for built-in POD types sets them to zero).

What is a declaration is (and thus when assignment is used).

The assignment operator only applies when the object on the left hand side of the = has been fully constructed. Since the object in a declaration is not fully constructed until the ';' this is not an assignment.

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).

This is construction of an object from a temporary using the copy constructor. But that does not matter because the compiler just elides the actual copy and builds in place so I would be totally surprised if there was any copy happening.

What is happening in your code

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

The same rules apply to classes with compiler generated default constructors:

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.

So what is the difference between Value/Zero Initialization?
Value Initialization will do no initialization for built in POD types. It will call the Value Initialization compiler generated default constructor on any base classes and members (Note if you define a default constructor then it will use that because there is no compiler generated one).

Zero Initialization will do zero initialization on POD types. It will call the Zero-Initialization compiler generated default constructor on any base classes and members (Note if you define a default constructor then it will use that because there is no compiler generated one).

Community
  • 1
  • 1
Martin York
  • 257,169
  • 86
  • 333
  • 562
  • I've read your *"But that does not matter"* and I agree! You also said you thought it was not constructing from a temporary but you had to look it up, so I looked it up and posted the results :( The case btw. is slightly different for class- and non-class types, so you're even right for `int z = int()` no temporary object is created (yeah.. THAT'S an irrelevant detail ;) Your explanations are even more concise now than before! The other point (value-init vs. default-init) is probably just a naming issue, but I think it's more important. – dyp Oct 28 '13 at 16:43
  • Please excuse me to touch this again, but *value initialization* **will do** initialization for POD types: it will do zero-initialization. However, *default-initialization* won't do any initialization for POD types. It will call the default-ctor for POD classes but this ctor is trivial and doesn't initialize any members. `some_type x;` is default-initialization, `some_type()`, `some_type{}` and `some_type x{}` invoke value-initialization. – dyp Oct 28 '13 at 16:52
  • DyP: Always in a bad mood when I get up. :-) All fixed now. Its just the accepted answer at the moment is wrong that annoys me. – Martin York Oct 28 '13 at 19:20
  • Well I wouldn't say it's wrong, but I agree it's just unnecessary to create a ctor (umm yeah they *will* be initialized by zero-initialization, if that's invoked). And it can have surprising results: Consider `MyClass() {}`, which will *prevent* zero-initialization, even for `MyClass x{};`. "All fixed now" *rubseyes* - *F5* - *scratcheshead* answer: *edited 4 hours ago* comment: *DyP 2 hours ago* o.O – dyp Oct 28 '13 at 19:38
2

You can simply do:

MyClass item1;

It may or may not zero initialize the POD type depending on the variable.

Or if you use C++11, you can do:

MyClass item1 {};

to explicitly call the default constructor.

The reason why there is no assembly code for item1 is because the compiler thinks it is not necessary to generate code for a class like the one shown since it has no explicit default constructor written by you.

SwiftMango
  • 15,092
  • 13
  • 71
  • 136
  • That's the thing -- I'd expect both item1 and item2 to have their 'y' value initialized to whatever the default constructor decides. If they're both calling the default constructor, how did their 'y' values become different? – Ben Walker Oct 28 '13 at 02:58
  • @BenWalker - the value of y in item1 and item2 can be anything. Both different is also anything. You cannot also depend on it being different. It can also be the same. In general, if you want y to be initialized, then initialize it by having a default ctor which looks like ` MyClass() : y(0) { }` – user93353 Oct 28 '13 at 03:12
  • 1
    There are two default constructors generated. Your first line use the "Value-Initialization" compiler generated default constructor. This will not initialize built in POD types. Thus the member y is left uninitialized. The C++11 version is perfectly correct and will initialize the member to zero. – Martin York Oct 28 '13 at 05:20
  • @LokiAstari yes you are right and i edited my answer. However POD type will be zero initialized if it is a global variable or static.. – SwiftMango Oct 28 '13 at 06:09