Introduction

If you’ve ever programmed in C++ before, you undoubtedly know what constructors and destructors are.  However, when dealing with POD (plain old data) types, such as a vector/matrix class or other low-level data object, it is important to understand what impact there is with explicitly defining a constructor and/or destructor for your class.  In this article, I will explain what trivial constructors and destructors are, and how they differ from explicitly defined constructors and destructors.

The Standard’s Definition

As defined by the C++ standard, any struct or class type, or even primitive type is required to have a constructor and destructor (as well as a copy constructor and assignment operator, but we won’t go into that in this article).  If one is not explicitly defined for that type, then the compiler automatically generates the necessary function in its place.  Like an explicitly defined constructor, a generated constructor is responsible for calling the constructors for any base classes, and the constructors on any non-trivial non-static member variables.

That was certainly a mouthful.  If we look at that last statement from the opposite end, it becomes easier to understand by defining what a trivial constructor is instead.  Therefore, a constructor is considered trivial if:

  • Any member variables (non-static) are either primitive types or their constructors are trivial, and
  • Any classes derived from also have trivial constructors.

Likewise, a destructor is considered trivial if:

  • Any member variables (again, non-static) are either primitive types or their destructors are trivial, and
  • Any classes derived from also have trivial destructors.

The important point to note here is if either the constructor or destructor in a class or struct is trivial, then the compiler is free to completely optimize away the calling of that function.  As an added bonus, if both the constructor and destructor are trivial, then you are free to use the type inside of a union! (C++0x has already rectified this issue, however, and the standard has been relaxed to allow types with a non-trivial constructor to be used within a union.)

A Typical Vector Class

First of all, let’s look at an implementation of a typical and very simplistic vector math class (containing only an addition operator for brevity):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Vector
{
public:
	Vector(void);
	Vector(float vx, float vy, float vz);
	Vector(const Vector &vec);
	Vector &operator +=(const Vector &vec);
	Vector operator +(const Vector &vec) const;
 
public:
	float x, y, z;
};
 
Vector::Vector(void)
{
}
 
Vector::Vector(float vx, float vy, float vz) : x(vx), y(vy), z(vz)
{
}
 
Vector::Vector(const Vector &vec) : x(vec.x), y(vec.y), z(vec.z)
{
}
 
Vector &Vector::operator +=(const Vector &vec)
{
	x += vec.x;
	y += vec.y;
	z += vec.z;
	return *this;
}
 
Vector Vector::operator +(const Vector &vec) const
{
	// return a vector using the class constructor
	return Vector(x + vec.x, y + vec.y, z + vec.z);
}

Even though this class contains a do-nothing default constructor and no destructor, in C++ it cannot be used in a union.  As an added impact, if this code is placed in its own translation unit, creating an array of Vector objects will implicitly require the defined do-nothing constructor to be called for each element allocated.  For example, this seemingly innocent code:

1
2
3
4
5
6
int main(void)
{
	Vector *vec = new Vector[1000];
	// ...do something interesting with 'vec'...
	delete []vec;
}

would require calling the do-nothing constructor 1000 times, and if there was a destructor, it would also be called 1000 times as well!  This is rather obvious once it’s pointed out, but it is something easily to not take into consideration when designing a class.  Nowadays, however, compilers are also smart enough to optimize out calling of an empty function if it is properly defined as inline within a header file.

Do You Really Need to Initialize That?

Let’s take a step back and ask ourselves something first.  If we already have a default constructor which does nothing and takes no parameters, why do we even have any constructors at all?  Is it just for convenience so we can create a Vector with 3 parameters?

If that’s the only reason, then there really is no need to have any constructors.  Once a type becomes trivial (assuming the assignment operator is trivial as well), the compiler considers the type as a POD, and member initialization can be performed using C-style initializer lists.  Let’s make another attempt at our Vector class, this time without any constructors at all:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Vector
{
public:
	Vector &operator +=(const Vector &vec);
	Vector operator +(const Vector &vec) const;
 
public:
	float x, y, z;
};
 
Vector &Vector::operator +=(const Vector &vec)
{
	x += vec.x;
	y += vec.y;
	z += vec.z;
	return *this;
}
 
Vector Vector::operator +(const Vector &vec) const
{
	// create a vector using a C-style initialization list
	Vector res = { x + vec.x, y + vec.y, z + vec.z };
	return res;
}

As can be seen from the operator + implementation, instead of returning the result of a constructor, a C-style initialization list is used instead, and the value is returned directly.  Effectively, the compiled assembly code of the two versions are identical.  Also since Vector is now a POD type, it is free to be used in a union.  As an active working example, I personally make use of unions within the graphics pipeline with code similar to the following (I actually make use of templates, so it’s a bit more to look at):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct Transform
{
	union
	{
		struct
		{
			Vector	translation;
			union
			{
				Vector	rotationEuler;
				Quat	rotation;
			};
			Vector	scale;
		};
 
		Matrix	matrix;
	};
 
	bool	isEuler, isMatrix;
};

Utilizing unions in this way allows one to be flexible while lessening the amount of memory required, only storing the absolute minimum data necessary for an object.  For example, if a quaternion rotation is stored in the above transform, then there is no need to also store a euler rotation.  Likewise, if a matrix is stored, then there is no need to also store a complete translation/rotation/scale tuple either, so it saves on memory.

Having Your Cake and Eating It Too

So you’ve decided it might be neat to convert some of your types to be trivial to remove any doubt of a constructor/destructor being called, and perhaps the idea of using types within a union is appealing as well.  But you can’t quite shake the fact that you can’t use constructors anymore.

It turns out it actually is possible to continue to utilize constructors with POD types: by deriving from them.  If we keep everything necessary for the Vector class within the POD Vector itself, and only define constructors in the derived class, then both can be used interchangeably.  Let’s take one last shot at our Vector class again, this time adding a new VectorC class (I simply chose ‘C’ for constructor) for only the constructors we want:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Vector
{
public:
	Vector &operator +=(const Vector &vec);
	Vector operator +(const Vector &vec) const;
 
public:
	float x, y, z;
};
 
class VectorC : public Vector
{
public:
	// provide a constructor for VectorC
	VectorC(float vx, float vy, float vz)
	{
		x = vx;
		y = vy;
		z = vz;
	}
};
 
Vector &Vector::operator +=(const Vector &vec)
{
	x += vec.x;
	y += vec.y;
	z += vec.z;
	return *this;
}
 
Vector Vector::operator +(const Vector &vec) const
{
	// return an initialized Vector object
	return VectorC(x + vec.x, y + vec.y, z + vec.z);
}

And that’s really all there is to it.  The compiled assembly code is again identical to the previous implementation, so you can have the best of both worlds.

Conclusion

By eliminating unnecessary constructors and destructors in your code, you’ll not only save some bytes in code size, but you might also get a faster running executable as well.  Of course I’m not advocating removing the constructor and destructor from every single class you’ve ever created, but you should certainly consider whether a given class should have an explicit constructor and destructor defined, instead of just continuing to define them out of habit.

Considering that this is my first blog post here at #AltDevBlogADay (actually my first blog post anywhere for that matter!), I decided to start out with something simple, so I apologize up front if this post seemed rather basic.  Once I get the hang of posting around here, I’ll start ramping up with more interesting and in-depth articles.  Of course if you should have any suggestions for future topics, I’ll be more than happy to dive into them.  Let me know in the comments!