Discussion: A False Start

Execute the code to understand the output and gain insights into object and resource management.

Run the code

Now, it’s time to execute the code and observe the output.

Press + to interact
#include <iostream>
#include <stdexcept>
struct Engine
{
~Engine() { std::cout << "Engine stopped\n"; }
};
struct Machine
{
Machine() { throw std::runtime_error{"Failed to start the machine"}; }
~Machine() { std::cout << "Machine stopped\n"; }
Engine engine_;
};
int main()
{
try
{
Machine machine;
}
catch (...)
{
}
}

Understanding the code

In this puzzle, we have a Machine class that contains an Engine. But Machine throws in its constructor before it’s completely done initializing. Does the engine get stopped? Does the Machine itself get stopped?

Object destruction

One of the great things about C++ is that constructed objects get destroyed automatically and deterministically (unless we manually call new, which these days should be left to library authors). We’ll find no need for Python’s with or C#’s using, and rarely a need for something like finally.

But only constructed objects get destroyed, so what does it mean to be constructed? And what happens in the case of half-constructed objects like the Machine instance here, which throws during construction?

Order of object initialization

An object is only deemed to be constructed when initialization has been completed. The initialization of a class object goes in this order:

  • Virtual base classes

  • Direct base classes

  • Non-static data members (like Machine::engine_)

  • The constructor body

Initialization of Machine

So, when we’re constructing the Machine object, we first initialize the Engine member. As soon as this is done, engine_ is deemed constructed and will eventually have to be destroyed. Then, we get to the Machine constructor body, which throws an exception. The initialization of Machine did not complete, and it’s not deemed constructed. Only the Engine destructor gets called, not the Machine destructor.

This is very useful since we don’t have to think about cleaning up half-initialized objects. Any initialized member like engine_ gets destroyed automatically; the rest don’t need to be since they were never constructed in the first place. We do have to be careful, though, if we manually acquire some resources in the constructor. In that case, we can’t rely on the destructor to clean it up. For instance, if we call new and assign the pointer to a member, we cannot rely on a delete in our destructor to clean it up. The same goes for raw calls to function pairs like open/close.

Automatic resource management

As always, to make our code more robust, avoid manually calling functions like delete and close. Instead, use standard library types like unique_ptr or fstream as members, which automatically clean up the managed resources in their destructors. If we have several such resource members, the ones that got initialized—and only those—will be automatically released even in the case of a half-constructed object like machine.

If our class manages a custom resource that doesn’t fit in a standard library type, such as the engine in this puzzle that needs to be stopped, factor that responsibility out to a separate small class like Engine instead of handling it manually in the containing class. Then, we don’t need to think about managing it at all; it will always be destroyed if it is constructed.

Recommendations

Here are some recommendations to ensure proper resource management and avoid memory leaks.

  • Avoid manual resource management: Use standard library types like unique_ptr and fstream as members to automatically manage resources and prevent leaks.

  • Encapsulate custom resource management: For custom resources, create dedicated classes focused solely on managing that specific type of resource, ensuring proper handling and cleanup.

Level up your interview prep. Join Educative to access 70+ hands-on prep courses.