Zero initialization with optional constructor in c++

Started by
8 comments, last by frob 6 years, 6 months ago

I like having all my member variables in my classes/structs to be zero always.

In the past i have used this style in Ansi-C a lot without any problems:


// On the stack
MyStruct valueS = {};

// On the heap or from another memory
MyStruct *valueH = ...
*valueH = {};

 

Now i am switching to C++ and wanted to do the same kind of trick. And for simple stuff this works, but for some other cases it wont.

For example if i use a class/struct as a member in my struct which have a default constructor, this wont work anymore and i get a compile error like this:

Quote

E1790    the default constructor of "Input" cannot be referenced -- it is a deleted function

Input do not have any constructors at all, but there are a Controller array stored there and the Controller have a Vec2f - which have constructors.


		union Vec2f {
			struct {
				f32 x;
				f32 y;
			};
			struct {
				f32 w;
				f32 h;
			};
			f32 elements[2];

			inline Vec2f() {
				x = y = 0;
			}
			inline Vec2f(f32 value) {
				x = y = value;
			}
			inline Vec2f(f32 newX, f32 newY) {
				x = newX;
				y = newY;
			}
			inline Vec2f(const Vec2f &from) {
				x = from.x;
				y = from.y;
			}
		};

		struct ButtonState {
			b32 isDown;
			s32 halfTransitionCount;

			inline bool WasPressed() const {
				bool result = ((halfTransitionCount > 1) || ((halfTransitionCount == 1) && (isDown)));
				return(result);
			}
		};

		struct Controller {
			u32 playerIndex;
			Vec2f movement;
			union {
				struct {
					ButtonState actionUp;
					ButtonState actionDown;
					ButtonState actionLeft;
					ButtonState actionRight;
				};
				ButtonState buttons[4];
			};
		};

		struct Input {
			f32 deltaTime;
			u32 playerOneControllerIndex;
			union {
				struct {
					Controller keyboard;
				};
				Controller controllers[1];
			};
		};

		void DoStuff() {
          	Input inputs[2] = { }; // <- Compiler error
          	Input *currentInput = &inputs[0];
			Input *prevInput = &inputs[1];
        }

 

So my question is, how can i do the same trick (Clearing everything) to zero, but makes sure the default constructors for all fields are called?

 

I know weird question, most people just initialize everything in the constructor... but i hate doing that, unless its meant to be non-zero.

 

Adding default constructors to all my classes makes the compiler happy, but not me, because nothing gets initialized to zero when i use "something = {};"

 

Advertisement
1 hour ago, Finalspace said:

I know weird question, most people just initialize everything in the constructor... but i hate doing that, unless its meant to be non-zero.

You're already doing that (without using the "technically faster" initialization lists, either) in various places in this code, though. You're also going to have to do that in some cases in C++. 

1 hour ago, Finalspace said:

So my question is, how can i do the same trick (Clearing everything) to zero, but makes sure the default constructors for all fields are called?

 

They'll be called if they exist (including if they were generated defaults). The error is telling you that one of the default constructors (for Input) was deleted. It was deleted because the presence of the union in the type means it cannot be generated. You'll have to provide an explicit default constructor for any of these types that have unions in them, as you did with the vector type (which, incidentally, used in the fashion you're implying technically involves undefined behavior -- but does generally work the way you are expecting on all major compilers).

You're not going to be able to program modern C++ with all the syntactic equivalencies of C you'd like because they are quite different languages in areas where they seem syntactically the same. Initialization is one of those.

19 minutes ago, jpetrie said:

You're already doing that (without using the "technically faster" initialization lists, either) in various places in this code, though. You're also going to have to do that in some cases in C++. 

They'll be called if they exist (including if they were generated defaults). The error is telling you that one of the default constructors (for Input) was deleted. It was deleted because the presence of the union in the type means it cannot be generated. You'll have to provide an explicit default constructor for any of these types that have unions in them, as you did with the vector type (which, incidentally, used in the fashion you're implying technically involves undefined behavior -- but does generally work the way you are expecting on all major compilers).

You're not going to be able to program modern C++ with all the syntactic equivalencies of C you'd like because they are quite different languages in areas where they seem syntactically the same. Initialization is one of those.

I dont want to write C code anymore, i just wanted to clear my struct/class fields it to zero and then let the default constructor being called - is that so hard?

And yes i know about the initialization list, its that thing:


Constructor() : 
  fieldA(0), 
  fieldB(0), 
  fieldC(0), 
  classWhatever() {

}

 

As jpetrie said, you cannot call the default constructor, because there is none auto-generated. Once you define a default constructor, you can.

8 minutes ago, Finalspace said:

I dont want to write C code anymore, i just wanted to clear my struct/class fields it to zero and then let the default constructor being called - is that so hard?

Only if you make it hard; the mechanism to do this is to zero them in the constructor or (better) in the constructor's initialization list. If you try to avoid using that mechanism on principle, then yes, it will be difficult. In modern-enough C++ you can initialize some members inline in the class declaration, but that's effectively just a syntactic difference. The actual initialization still happens at the same effective place in time.

The other alternative is to avoid using constructs (like unions) which affect the triviality or layout of the type, allowing you to continue to use zero initialization.

2 hours ago, jpetrie said:

Only if you make it hard; the mechanism to do this is to zero them in the constructor or (better) in the constructor's initialization list. If you try to avoid using that mechanism on principle, then yes, it will be difficult. In modern-enough C++ you can initialize some members inline in the class declaration, but that's effectively just a syntactic difference. The actual initialization still happens at the same effective place in time.

The other alternative is to avoid using constructs (like unions) which affect the triviality or layout of the type, allowing you to continue to use zero initialization.

I gave up, i had setup the initializer list and initialize arrays in the constructor now.

Reason: I got internal compiler errors and i dont care anymore. Want to make a game, not fighting against the language.

2 minutes ago, Finalspace said:

I give up, i have setup the initializer list and initialize arrays in the constructor.

Reason: I got internal compiler errors and i dont care anymore.

I prefer to initialize in the struct/class itself.  It's easier for users of it to see what the default values are.


struct Vec2f
{
	// Functions...

	// Default initalize:
	float x = 0.0f;
	float y = 0.0f;
};

Just like @jpetrie said, this will initialize in the same place as in the constructor and at the same time, but I feel it's more self documenting and just plain easier to read (instead of having to look at a constructor that might be coded in a .cpp file).

 

"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin

11 hours ago, Mike2343 said:

I prefer to initialize in the struct/class itself.  It's easier for users of it to see what the default values are.



struct Vec2f
{
	// Functions...

	// Default initalize:
	float x = 0.0f;
	float y = 0.0f;
};

Just like @jpetrie said, this will initialize in the same place as in the constructor and at the same time, but I feel it's more self documenting and just plain easier to read (instead of having to look at a constructor that might be coded in a .cpp file).

 

Oh okay, i didn´t knew that. Thanks for that tip, then i can use that style for now. Its much better than the initializer list.

Initializer lists are still the best option when values come in as parameters.  The initializer list will be used instead of the default values.

Really all of this is to avoid the double initialization which you'd see with zero initialization.  The point is to avoid multiple unused assignments.  If you can assign it to the correct value the first time --- which might not be zero --- then do so. That's better than assigning it to zero the first time, then assigning it to another value the line afterword.  On the flip side, that also means not bothering to set values if you know they'll never be read, such as not bothering to zero the contents of an empty memory buffer while you've got a marker saying zero bytes are used.

Initialize them to whatever you need them to be, but don't worry TOO much if you make a duplicate assignment or if there is duplication between the initialization list and the constructor body. For most of these types the cost is a fraction of a nanosecond. Not quite zero and something you should think about when writing code, but there are much bigger issues to address.

This topic is closed to new replies.

Advertisement