The constructor is a special method which is executed during the construction of the object. It has all the characteristics of a regular method, including visibility, except one:

  • if an exception is thrown in a constructor, the construction of the object fails

This is a powerful effect to which I'll come back later.

Finally, the name “constructor” is not really what it is, because by the time the execution flow is inside the constructor, the object has already been constructed (allocated) – think about the “this” or “self” pointer already set. In my opinion, a better name would be “initializer”.

How to use the constructor

The task of the constructor should be to initialize the object in a valid state. If the constructor cannot do that, the next best thing is to stop the creation of the object by throwing an exception.

In order to understand the benefits of doing so, we have to take a step back and think about the bigger picture.

In an OOP project, we have classes as blueprints to create objects, and methods which allow objects to send messages to other objects and get back results, if any.

Let's say we have the following objects: a Caller, a Message, a Callee and a method Callee::doSomething(Message m).

Let's think about the Message constructor. If it throws an exception, because its own data is invalid, then we don't have an object to pass to doSomething in the first place, meaning that the stack trace is actually useful: it shows us exactly where the program has crashed and for what reason.

Would we just allow the creation of an invalid object of type Message, we would have to sprinkle in doSomething conditions to check whether the message is valid. If you do this check only once in the lifetime of an object, let's say it's ok, but as it usually goes, new requirements are going to come in and you're going to add the same if over and over again in different new methods. See also Reduce the number of ifs.

For this reason, the best way to deal with this is to crash early, and get the most out of the context of the exception by handling it at the appropiate level of abstraction.

In the rationale above I've also mentioned another concept – putting the object in a valid state. There is a extension to this idea saying that all objects should be valid at all times.