Different ways to initialize structs/unions C/C++

I've read K&R and followed some C++ tutorials but am still very green when it comes to both languages.

In our project so far, I've noticed we've been using a number of different syntaxes for initializing structs and unions, but couldn't always understand why we used one over the other in the actual code.

From a little experimenting in the compiler, I have some idea how they work, but could someone point out any mistakes I've made, or important differences I've overlooked?

#1:
1
v2 v = {};

This way seems to be Casey's favorite, and if I'm not mistaken creates a v2 on the stack and initializes all members to 0;

#2:
1
v2 v = {0, 1}

Like the first method, but individually initializes the members in order. If you don't include all the members, the rest seem to be initialized to 0 but is that guaranteed?

#3:
1
v2 v = V2(0, 1);

This is just using a specific function to create a new v2 and initialize it with the arguments passed. Not sure what the benefits are for this method.
I guess if we wrote:
1
v = V2(0, 1)

We would be creating a new v2, whereas if we wrote:
1
v = {0, 1}

We would still be using an old one?

#4:
1
v2 v = v2{0, 1}

I think this might be newer C++ syntax? Otherwise this behaves in the same way as using the function V2, in that it always creates a new v2? On my computer, VS intellisense complains that:
'Error: braces cannot be omitted for this subobject initializer' but I think that may be a bug as it compiles fine.

#5:
1
entity Entity {};

This seems to be the same as #1 - create a new struct and init to 0.
I'm not familiar with #4 maybe you meant to type

1
v2 v{0, 1};


instead

edit: nvm, I can't read, that's pretty much your #5 and you said even #4 compiles. The fact that ItelliSense flags something that compiles fine is no surprise since I read that ItelliSense is completely seperate from the compiler and does not share much code, it's a completely different lexer,parser,etc.

Edited by Bigpet on
#1, #2 - yes, you are correct. It is guaranteed that all structure members after last specified are initialized to 0.

#4 and #5 is C++11 syntax. It uses uniform initialization syntax where you can initialize with { .. } anything (array, constant, object, POD structure).

#3 advantage of this is to use in arbitrary expressions, not only initializations
1
a = V2(0,1) + V2(width, height);


And this:
1
v = {0, 1}

is C++11 syntax (so it is more in #4 category). It will work only when assigning value, not in arbitrary expression.

And you shouldn't look at that if it "creates" new variable or assigns new one. Compiler will optimize that all away anyway, and will use value passed to V2 function directly.

#4 and #3 could be used in each others place, it's just what is your preference in syntax. Advantage of #3 is that it will work even with C compiler.

Edited by Mārtiņš Možeiko on
Thanks a lot for the replies, very helpful. There seem to be many ways of doing similar things in C++ it can get a bit confusing, but I'm glad I wasn't completely misunderstanding.
I just read the chapter about this in Scott Meyers's latest book ( http://shop.oreilly.com/product/0636920033707.do ). Without reprinting the whole chapter here, he basically says there's these types of initialization statements in C++11:

1s) v2 a(1, 2); // requires you to write the constructor
2s) v2 b = v2(3, 4); // this is basically what Casey is doing, except with a standalone function rather than a constructor.
3s) v2 c{ 5, 6 };
4s) v2 d = { 7, 8 };

I suggested this one to Casey based on the way he was using his vector initialization function: v2 vec = v2{ 1, 2 }; Basically, replacing: v2 vec = V2( 1, 2); as closely as possible to his original syntax. However, I'd point out that neither approach, Casey's or my suggestion, are basic C++ initialization.

Casey's version uses a function to initialize a v2 structure based on the function arguments. The new v2 struct is returned from the function and assigned (copied) to the new vec being created. Because of several compiler implementation details, of which Casey is aware of and takes advantage of, his approach compiles down to the same machine code as any of the simple initialization syntaxes when building with optimizations enabled. Similar things would happen with the version I suggested. If you're a horrible nerd, you might be interested in these:

https://en.wikipedia.org/wiki/Return_value_optimization
https://en.wikipedia.org/wiki/Copy_elision

Back to Scott's initialization types:

v2 a(1, 2); I believe that Casey wanted to avoid using constructors in his code base, so this one is probably off the table, at least for his stream.

v2 b = V2(3, 4); Using the assignment operator is nice, but requires you to create a constructor or factory function.

v2 c{ 5, 6 }; What Scott calls "braced initialization". This C++11 style prevents two legacy C/C++ problems: C++'s "most vexing parse" (I doubt this often happens to Casey based on his programming style), and narrowing conversions (which, VS will generate a warning for using the previous two syntaxes, and maybe you don't care so much with simple vectors... I don't know). Anyway, this syntax works almost everywhere with few downsides. Personally, I would recommend this syntax for new C++ code, unless you are creating constructors that take std::initializer_list all over the place. If you don't know what std::initializer_list is and you're sticking mostly to what you see on Casey's stream it's not worth talking about here. Otherwise, if you're using the STL a bunch, there's more to know; I'd recommend you pick up Scott's book to learn the details.

v2 d = { 7, 8 }; This is basically the same as the previous option only with an additional assignment operator. It sort of seems like extra typing for no good reason, and doesn't quite work in as many places as the previous syntax. But, it's an option with C++11 compilers.

In reference to mmozeiko's comment: #3 and #4 will behave differently if you attempted to construct a new v2 with parameters that would cause a narrowing conversion. For example:

v2 b = V2(3.14f, 3.14f); // Compiles, no warnings.
v2 b = V2(3.14, 3.14); // Compiles, but causes VS to issue a warning.
v2 c{ 3.14f, 3.14f }; // Compiles, no warnings.
v2 c{ 3.14, 3.14 }; // Fails to compiles, error: narrowing conversion.

Braced initialization won't allow you to convert a double to a float without a cast.
ChrisG0x20
v2 d = { 7, 8 }; This is basically the same as the previous option only with an additional assignment operator. It sort of seems like extra typing for no good reason, and doesn't quite work in as many places as the previous syntax. But, it's an option with C++11 compilers.


No, this is not same as previous. This is not even C++. This is pure C syntax. You can initialize structure members by simply putting values consequently in curly braces. C99 standard allows designated initializers if you want explicitly say to what members assign what values (VS2013 surprisingly supports this C99 feature):
1
v2 d = { .y = 8, .x = 7 };
Well "v2 d = { 7, 8 };" is a C++ thing, just not the same as the C thing with the same syntax. For aggregate types (aka POD types) it pretty much functions the same as C (but no designated initializers in the C++ standard).

If however "v2" is not a POD type then it tries to find a constructor that takes an std::initializer_list of the right type, if there's none it checks if it has a constructor with the right amount of parameters and the right types. (and there's special cases if there's nothing in the braces or a single element of type "v2" or derived)

Anyway this is hard to describe in text, just look at the reference, for POD types:

http://en.cppreference.com/w/cpp/language/aggregate_initialization

and for non POD types number 6 here:

http://en.cppreference.com/w/cpp/language/list_initialization


edit: oh, and the POD thing worked before C++11, only the fact that it now works for non-POD types too is new to C++11.

Edited by Bigpet on
I apologize for not being totally clear about that one. Bigpet is totally correct about that syntax invoking constructors taking std::initializer_list.

But, to be clear about what I was referring to, say you have your struct defined like this with only this constructor (with no constructor taking a std::initializer_list):

struct v2 {
int x, y;

v2(int e0, int e1) : x(e0), y(e1) {}
};

Using this syntax in C++11/14:

v2 d = { 7, 8 }; // Actually uses the constructor.

It's weird... I know. I don't plan on using that type of syntax in my code, I just included it for completeness without a good explanation of how it was different in C++11 from previous versions of C++.