Contents
Sometimes we fail to acquire a needed resource or responsibility during the construction of an object. Sometimes the construction of a subobject fails. How can we deal with an incompletely constructed object?
Failing to establish invariants
So, what does it mean for our object-to-be if we can’t get what it needs to operate properly? What if we can’t establish one of the invariants we designed our constructed object to have?
Invariants define the nature of our class and its objects. You simply can not create an object of that class without any of the invariants – it would be an object, but either not of that class, or the invariants would not be part of the class’ definition. (The semantic definition that is, not the physical source code representation – we can’t define invariants in code yet).
So, how can we express in code that we were not able to establish the invariants for the object we were trying to create? We usually will notice that in the object’s constructor. One option would be to set internal state to some empty default value, but that means that either it is not a needed asset for the object but rather optional, or we are entering a bad state, which is not a thing we should want to deal with.
We can’t return `false` or `nullptr` or something similar either. Constructors can’t be exited early in a normal way. If the constructor finishes execution, the object is created. The only option is to throw an exception. An exception thrown from the constructor will destroy all already constructed subobjects. And the good part: The object under construction will never even start to exist, because object lifetime starts after the constructor is left normally.
Failing to construct subobjects
If we consider constructors that might throw, we have to be aware about the fact that the construction and initialization of subobjects may throw an exception. If that happens, the is not very much we can do about it. A part of our object-to be is missing, it never came to existance. What can we do?
It turns out, if a subobject throws an exception during its initialization, the constructor body of our object will never even start to execute. There is no way around it – the exception is thrown before the constructor body is entered, and we have no means to catch it and go on to execute the constructor body.
We can, however, catch the exception. There is a little known feature in C++ called function try block which basically allows us to wrap the entire execution of a function in a try block with a corresponding catch handler. For normal functions it does not much more than a try block containing everything inside the function, so it’s only achievement there is to look odd. In a constructor however, it also includes the initialization list and any implicit subobject initialization:
struct ThrowingCtor { ThrowingCtor() { throw std::logic_error("for no apparent reason"); } }; class MyClass { std::string s; std::unique_ptr<int> ui; ThrowingCtor tc; public: MyClass() try : s("uhoh this string is so long that we have to allocate some memory"), ui(std::make_unique<int>(42)) { std::cout << "ctor body\n"; } catch(std::logic_error const& le) { std::cerr << le.what() << '\n'; } catch(std::bad_alloc const& ba) { //uhm. not much we can do here. no memory... } };
We have examples of everything that might fail here: `string`’s constructor has to allocate memory which might fail. `make_unique` also allocates memory, so the initialization of `ui` can fail even before the constructor is called. And in the end, the construction of `tc`will fail. The already constructed objects will be properly destroyed during stack unwinding, and we enter the catch handler for the `logic_error`.
If you try this, e.g. just construct a `MyClass` object in `main()`, you may be in for a surprise: this constructor will emit an exception. “Wait what? We caught the exception and did not throw it again!” you may say – but here comes a little specialty for function try blocks in constructors: if there is an exception, and the catch handler does not throw anything, the original exception will be rethrown when leaving the catch handler.
If we think about it, this is exactly the right thing that should happen. One of our subobjects could not be created. The others have been destroyed when the exception was thrown. We can’t create our object, because there is nothing left that could make up an object. So we have to throw something. So if we don’t do that explicitly, the compiler will do it for us.
Conclusion
If we can’t establish any meaningful state, we should throw an exception in the constructor. If we can’t create one of our subobjects, we have to throw an exception, either the one thrown by the subobject initialization, or another one – but we can’t get around it.
Permalink
Hi Arne, thanks for this post. After reading, I’m not sure yet how much I will use this feature, but it’s certainly great to know about!
Cheers,
Sebastian
Permalink
I find that C++ constructor function try blocks are a code smell. They are a sign that you have not used RAII adequately and are trying to manage 2 resources directly. In my experience, so far, everywhere I could have used them, I find that I am able to avoid them either using something like unique_ptr, or writing a small class that actually manages the resource.
Permalink
Just for the reference if anyone would like to see such behavior declared by C++ standard in N4296 15.3.14
Permalink
What about making cleanup in such a case?
For example: in constructor we pass this as an argument to some other object (Listener) . AFAIK destructor of our failing object will not be called as no object was constructed.
Permalink
In which case exactly? If the listener can not be constructed, then it’s destructor would not be called. It would not be needed, because since there is no listener, there’s nothing to clean up.
However, that means that of course constructors should be exception safe – if the listener constructor throws late in the constructor body, anything that has been done to its constructor arguments must be reverted. Something that never existed should not have a side effect.
Permalink
RAII to the rescue. Design a class that represents the connection between listener and subject, destructor of this new class will do the cleanup, disconnecting the listener from the subject. When subject passes the this pointer as argument to listener, listener returns an instance of this new connection class. Subject saves the connection as a member of itself. If subject’s constructor throws, the destructors of its members will be called.