Advertisement

Struct default initialization (c++)

Started by January 22, 2023 09:46 AM
6 comments, last by cozzie 1 year, 10 months ago

Hi,

I was wondering if the following 2 approaches produce identical output?
Personally I prefer the one with default initialization, but I'm not 100% sure if that ‘combines’ with the specified ctor. So basically, will the variables that are not in the initializer list, still get the the default value from the variable declarations in the struct?

struct TUPLE_CAMERA
{
	int CameraIndex				= -1;
	int TransformIndex			= -1;
	int RenderBucketIndex		= -1;
	int SkyboxMeshRendererIndex	= -1;

	TUPLE_CAMERA() { }

	TUPLE_CAMERA(const int pCameraIndex, const int pTransformIndex) : CameraIndex(pCameraIndex), TransformIndex(pTransformIndex) { }
};

Versus

struct TUPLE_CAMERA
{
	int CameraIndex;
	int TransformIndex;
	int RenderBucketIndex;
	int SkyboxMeshRendererIndex;

	TUPLE_CAMERA() : CameraIndex(-1), TransformIndex(-1), RenderBucketIndex(-1), SkyboxMeshRendererIndex(-1) { }

	TUPLE_CAMERA(const int pCameraIndex, const int pTransformIndex) : CameraIndex(pCameraIndex), TransformIndex(pTransformIndex), RenderBucketIndex(-1), SkyboxMeshRendererIndex(-1) { }
};

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

Yes, writing the default-value inline in the variable-declaration (forgot how thats called exactly) combines with the “overrides” in the ctor. Meaning that, every value you specify in that ctor gets that value there, and all else get the default from the variable-declaration. So in general it is preferable to write the initializers like you did in your first example, with the only real downside being that you need to change the .h-file whenever you change the value (which is also only a difference if you actually move the ctor-definition inside a .cpp; unlike your second example).

Advertisement

They should be the same. The second one (which I mostly use because I'm used to it) is older. The downside of it is if you have a lot of constructors you have to do a lot of repeat initialization. I guess I still like it better because you always find your initialization with the constructor, and you don't have to go looking at the header and combine the two. But it mostly comes down to personal preference, so pick your poison.

Thanks for the fast responses

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

The real benefit of the first one is that even if you have a lot of constructors if you forget to initialise a value in one of them, it will have a an intial value becasue you gave it at declaration time.

Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, theHunter, theHunter: Primal, Mad Max, Watch Dogs: Legion

It is going to be “maybe”, until you can specify exactly what compiler you use, all the settings, and the code around it.

The language standard makes few guarantees about how they are executed, and no guarantees about generated code.

Even though the language doesn't guarantee it, a good, modern, optimizing compiler will likely generate the same code between the two. Exactly what that code is will depend on the architecture, where the object is stored, how it is used, and what optimizations are enabled.

You might want to explore a bit with the Godbolt Compiler Explorer, most of the options are modern choices that give the same results. You can compare how different optimizations generate different code. For example, changing the optimization level like /O1, /Ox, /O2, /Od, and similar all produce different results.

As an easy example, the same code but three compiler settings gives different results using x64 msvc v19.latest. This is a decent modern compiler, and it happens to (but isn't required to) generate the same code for both options.

A default compile of /Ot or with the optimization option /Os or /Od gives:

  mov QWORD PTR [rsp+8], rcx
  mov rax, QWORD PTR this$[rsp]
  mov DWORD PTR [rax], -1
  mov rax, QWORD PTR this$[rsp]
  mov DWORD PTR [rax+4], -1
  mov rax, QWORD PTR this$[rsp]
  mov DWORD PTR [rax+8], -1
  mov rax, QWORD PTR this$[rsp]
  mov DWORD PTR [rax+12], -1
  mov rax, QWORD PTR this$[rsp]
  ret 0

with /O1 gives:

  or eax, -1
  mov DWORD PTR [rcx], eax
  mov DWORD PTR [rcx+4], eax
  mov DWORD PTR [rcx+8], eax
  mov DWORD PTR [rcx+12], eax
  mov rax, rcx
  ret 0

with /O2 gives:

  mov QWORD PTR [rcx], -1
  mov rax, rcx
  mov QWORD PTR [rcx+8], -1
  ret 0

They each happen to generate the same pairs of code for each, but that's through happenstance and not guaranteed by the language.

You can never know for sure what it generates until you look.

Advertisement

Thanks @frob , all clear.

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

This topic is closed to new replies.

Advertisement