Is that way of using constructor really better ?

Started by
28 comments, last by Servant of the Lord 11 years, 6 months ago

Personally if I have primitive variables that I need to initialise in a specific order, I'm not going to rely on the class definition order to do it, and that means good old fashioned assignment with the = operator.

And if I need bananas, I'm going to eat good old fashioned apples!

assignment != initialization wink.png

By the time your assignment is actually reached, your variables will have already been default-initialized (though it'd probably be optimized out* if the variable isn't accessed before your assignment).

*[size=2]Which is less a guarantee than the standard-guaranteed initialization order.

Why wouldn't you rely on the standardized initialization order? The only reason I can think of, is third-party APIs that have weird initialization requirements, requiring you to be 100% sure the API is initialized before you can, for example, load resources. This forces you to break out some initialization outside of the constructor entirely, into separate Init() functions, mucking up RAII for any globally-constructed variables.
But even in that situation, your assignments would still need to happen completely out of the constructor, and it wouldn't be a matter between constructor-body or initializer list, but between the entire construction process or a separate member-function called later.
Advertisement

Why is this being voted down? His gripe about the initialiser list order not being the actual initialisation order is perfectly valid. It's intuitive to assume that the variables are going to be initialised in the order that the initialisation code is written - which is exactly the case whenever you initialise something using the equals sign.


The only time the equals sign has anything to do with initialization is if it is done at the same time as the declaration. So it is still the declaration order that matters. Or to point it out very clearly

int x;
x = 10; //This is NOT initialization

int x = 10; //THIS is initialization... also technically a special case syntax for "int x(10);"



Also mentioned in this topic, ordering your members for alignment or caching reasons can potentially break your code if one member was initialised using the value of another.[/quote]

Which would seem like a very good reason not to do that, instead of saying "I'll simply not do proper initialization, so I can make something work, that shouldn't be done in the first place." How many times do you have a situation, where it's not just as easy to initialize both members by the same parameter/default value? And if one value is calculated from the other, how likely is it that one of those isn't redundant and it is vital for your performance to precalculate them both?
f@dzhttp://festini.device-zero.de

And if I need bananas, I'm going to eat good old fashioned apples!

assignment != initialization wink.png

By the time your assignment is actually reached, your variables will have already been default-initialized (though it'd probably be optimized out* if the variable isn't accessed before your assignment).


And if something smells like banana, tastes like banana and looks like banana, then it probably is banana, even if it is advertised as an apple...
For all primitive types - and that included pointers - assignment is initialization. For all practical purposes. And programming (as opposed to theoretical CS) is practical discipline.

Btw, I find it very disappointing, that people are downvoting Ryan_001 for a shortcoming in C++ standard and suggesting practical solution to overcome it. Many people are using only subsets of C++ standard for various reasons - and calling them bad programmers, summer students or saboteurs because of that is pointless insulting.
Lauris Kaplinski

First technology demo of my game Shinya is out: http://lauris.kaplinski.com/shinya
Khayyam 3D - a freeware poser and scene builder application: http://khayyam.kaplinski.com/

[quote name='taz0010' timestamp='1349502023' post='4987326']
Personally if I have primitive variables that I need to initialise in a specific order, I'm not going to rely on the class definition order to do it, and that means good old fashioned assignment with the = operator.

And if I need bananas, I'm going to eat good old fashioned apples!

assignment != initialization wink.png

By the time your assignment is actually reached, your variables will have already been default-initialized (though it'd probably be optimized out* if the variable isn't accessed before your assignment).

*[size=2]Which is less a guarantee than the standard-guaranteed initialization order.[/quote]I emboldened the key part in the above quote -- primitive types aren't default initialized, so when it comes to assigning their default values, then the choice of using the initializer list, or assignment in the constructor body is purely a choice of style. They both generate the same results (unless of course, another item in the initializer list is initialized using the value of a primitive member), so saying one is more correct than the other is nothing more than a style war.
Why wouldn't you rely on the standardized initialization order?[/quote]I've got a lot of classes that look like:
class Foo
{
Bar bar;
Baz baz;
public: Foo() : bar(42), baz(bar) {}
};
, which works fine -- bar is initialized before being passed into baz's constructor. As mentioned earlier, if the initializer list and the variable declaration orders are mismatched, then your compiler should warn you (in this case, a mismatch could cause baz to be passed an uninitialized variable). So far so good.

However, on every shipping game I've worked on, there comes a phase of panic during the final crunch period where the worst-case memory usage of the game is too high, and the QA department is able to produce out-of-memory errors (on consoles). One of the emergency measures that often happens at this point is some poor sod gets given the task of eliminated any unnecessary padding from everyone's data structures, which usually involves going through every class/struct and sorting all member declarations by their sizes (e.g. pushing all chars to the bottom of the class/struct).
Due to the C++ decision to enforce initialization in order of declaration this now causes a conflict -- the initialization list is written in the order that makes logical sense (e.g. so that bar is constructed before being passed to baz) and the declarations are ordered in a way to minimize padding, but C++ wants them both to be the same.
If said poor sod is to complete their assignment, they are now forced to depart from accepted C++ style and start writing horrible looking constructors to trick C++ into satisfying these two conflicting requirements.

I don't know the background behind why the C++ standardization people decided to enforce initialization in declaration order, instead of initialization in initialization order, but IMHO it was a mistake to make that decisiion. Initialization order is bound by logical constraints, but declaration order should only be a concern for memory layout, and should be decoupled from the concern of initialization.
N.B. more modern languages have gotten this right -- e.g. C# declaration order isn't even followed unless you manually specify that you require a certain layout, by default the compiler can rearrange your declarations to the most efficient layout, and allow you to initialize your members in their logical order.

I don't know the background behind why the C++ standardization people decided to enforce initialization in declaration order, instead of initialization in initialization order, but IMHO it was a mistake to make that decisiion. Initialization order is bound by logical constraints, but declaration order should only be a concern for memory layout, and should be decoupled from the concern of initialization.

IIRC, it's so that member variables would satisfy the last constructed, first destroyed order that is the general case for variables whose lifetime are managed by the compiler. In theory, the padding/member variable order issue was addressed by allowing the compiler to reorder position of member variables with different access specifiers, but in practice compilers don't actually take advantage of that.

IIRC, it's so that member variables would satisfy the last constructed, first destroyed order that is the general case for variables whose lifetime are managed by the compiler
Ah that makes sense -- if it went off the initializer list, and a class had 2 different constructors with different initialization orders, then the destruction order would no longer be the opposite (unless you error'd on mismatching initialization orders, or generated multiple virtual destructors).
That said, I'd still prefer the language to do that -- either generate an error if I've got multiple constructors with different orders, or simply warn me that my destruction order has become implementation defined due to the conflicting constructors.
In theory, the padding/member variable order issue was addressed by allowing the compiler to reorder position of member variables with different access specifiers, but in practice compilers don't actually take advantage of that.[/quote]Wait, the spec says the compiler is allowed to do that? Even after a decade of using this language I'm still learning new quirks...
C++03 allowed it anyways. I don't have a copy of C++11 handy right now, but I seem to remember it walked back that flexibility somewhat. The verbiage for C++03 goes:

Nonstatic data members of a (non-union) class declared without an intervening access-specifier are allocated so that later members have higher addresses within a class object. The order of allocation of nonstatic data members separated by an access-specifier is unspecified.[/quote]
Since it says "intervening access-specifier" you could, in theory, stick a public: or whatever in front of each and every member variable and the compiler could reshuffle them to minimize padding.

[quote name='taz0010' timestamp='1349502023' post='4987326']
Personally if I have primitive variables that I need to initialise in a specific order, I'm not going to rely on the class definition order to do it, and that means good old fashioned assignment with the = operator.

I emboldened the key part in the above quote -- primitive types aren't default initialized, so when it comes to assigning their default values, then the choice of using the initializer list, or assignment in the constructor body is purely a choice of style. They both generate the same results (unless of course, another item in the initializer list is initialized using the value of a primitive member), so saying one is more correct than the other is nothing more than a style war.[/quote]
Which is sortof the point I was poorly trying to make. Avoiding the initialization lists because it's 'not reliable' doesn't make sense to me, and being a choice of style, to call one less reliable when they are both standardized is misinformation. What you're saying though, is one is more resistant to side-effects of later code changes, and not that it's non-standard.

Personally if I have primitive variables that I need to initialise in a specific order, I'm not going to rely on the class definition order to do it, and that means good old fashioned assignment with the = operator.


I misunderstood taz's point - I thought he was implying that the class definition order is non-standardized, or unreliable. But you are saying that the humans during a crunch are unreliable, and easily mess up important initialization orders by mistake. That makes sense!

Offtopicly: Re-ordering the member variables by size seems rather silly to me (about as silly as re-ordering them based on alphabetizing their variable name), I've never worked in such an environment so there is probably have a good reason for it. Personally, in the few (having not worked on a compact platform) situations I needed to micro-manage the size of variables, I left comments mentioning each of their sizes. How does re-ordering by size help?

([size=2]I realize primitive types aren't initialized, and was mixing up initialization with construction - whoops!)
On most platforms types have alignment, a multiple of a memory address that it is most efficient to read or write that type. For primitives, alignment is generally equal to size. For structs and classes, alignment is generally equal to the alignment of the member variable with the most restrictive alignment requirements. On some platforms alignment is actually necessary to prevent a hardware exception; on other platforms it simply requires more clock cycles. For this reason C++ compilers will arrange structures so that every member variable is placed at proper alignment. Let's say you have the structure

struct A {
short aa;
int bb;
short cc;
};

With a two byte short and a four byte int, aa will generally be put at an offset of 0 into the struct, bb at an offset of 4 and cc at an offset of 8. Since A has an alignment of the most restrictive member, it will have an alignment of 4, and the whole structure will take up 12 bytes. However if you reorder the variables:

struct A {
short aa;
short cc;
int bb;
};

The aa will have an index of 0, cc an index of 2 and bb will have an index of 4, and the whole struct will take up only 8 bytes.
*slaps forehead* I've forgotten about alignment issues. Thank you for the explanation!

This topic is closed to new replies.

Advertisement