Aymeric on Software

Because we needed another of these blogs...

C++: Object Initialization and Error Handling

This column is in no way revolutionary. But looking back in the past, when I was learning C++, this is the kind of summary that I would have liked to read. So here it is, for the happy few!

In C++ there are two common idioms with regards to object construction and initialization.

  • the constructor fully initializes the object: this idiom makes it impossible to construct an object which is not correctly initialized and ready to use.

  • the constructor does not initialize (or partially initialize) the object: the initialization is performed at a latter stage by calling some init() method. This, in effect, splits the object allocation and initialization. It is common to encounter this pattern whenever the class mimics some transactional behavior like opening/closing a file, connection/disconnecting from a server…

One may have legitimate reasons to use one idiom rather than the other, but as a rule of thumb the first approach (full initialization in constructor) should be favored. This is because it makes it hard to use the interface of the object incorrectly. In the second case it is possible to construct the object and call a method on it before the initialization was performed. There are two ways to handle this improper usage:

  • do nothing and state that this is an “undefined behavior”. It will probably make the class a bit faster to write, but harder to debug. Not a good idea…

  • carry an internal state to remember whether or not the object was initialized. This make it possible to report errors in a much more graceful way. On the other hand it puts the burden on maintaining an internal state and checking against it. Maintaining an internal state looks very easy but my experience is that it is error prone in the long run…

So in summary, unless you have a good reason not to construct a fully initialized object, then you should. It will make your code harder to use incorrectly and much simpler. And you probably will have to write less.

There’s one issue though… Error handling.

There are three general ways to handle errors:

  • exceptions: this is the recommended and the best possible way.

  • return error code on each function: this is the classical C idiom. In practice it means every function you call returns an error code and that you MUST! check against it.

  • global error state: there is a function or method that you need to call after each method of the public API. This is similar to the role of the GetLastError() function in Win32. It’s quite unpractical, and very seldom used. Therefore, I will not discuss it any further…

In the case of a fully initializing constructor, it is not possible to use the “return error code” idiom, because the constructor does not return anything. That makes it mandatory to use an exception based system.

I worked in a large organization that essentially banned the use of exceptions in their code, for rather dubious reasons. The consequence of this move was that people were forced to use the “non initializing constructor” idiom almost everywhere. This made the code more verbose (init/terminate functions everywhere), the interface easy to use incorrectly (many bugs were related to calling methods on a non initialized object) and put the burden on maintaining an internal state on each object (many bugs were related to failing to maintain this internal state correctly). But the main source of bug was by far, people “forgetting” to check against the error code, leading to much more subtle bugs long after the point of error.

Sounds like a good idea to favor the fully initializing constructor isn’t it? If you use this idiom then you must know how to use exceptions properly. Techniques like RAII and smart pointers are mandatory. Here’s one reason:

If an exception is throw in the constructor of an object, the destructor of that object is not called.

So if you do something like in the (counter)example below, you are in troubles:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// class Child1 and Child2 defined earlier...
class Parent {
public:
    Parent() : m_child1(new Child1()), m_child2(new Child2()) { }
    ~Parent() {
        if (m_child1)
             delete m_child1;
        if (m_child2)
             delete m_child2;
    }
private;
     Parent(const Parent& ); // non copyable
     Parent &operator=(const Parent &); // non assignable
     Child1* m_child1;
     Child2* m_child2;
};

In this example if the constructor of Child2 throws and exception, then the constructor of Parent also throws. Since the destructor of Parent is not called, the newly constructed Child1 is not destroyed. It leaks!

However if you rely on smart pointers, the problem disappears. While the destructor of a class is not called if an exception is thrown in its constructor, the destructor of each instance variable is called. The following code would fix the leak:

1
2
3
4
5
6
7
8
9
10
// class Child1 and Child2 defined earlier...
class Parent {
public:
    Parent() : m_child1(new Child1()), m_child2(new Child2()) { }
private:
     Parent(const Parent& ); // non copyable
     Parent &operator=(const Parent &); // non assignable
     std::auto_ptr m_child1;
     std::auto_ptr m_child2;
};

That’s all folks!