Update. My remark on exceptional life-time of temporaries in array initialization was incorrect. This part is now fixed. I also included some essential information, as suggested by Herb Sutter.

C++, if you want to learn all of it, is big, difficult and tricky. If you look at what some people do with it, you might get scared. New features are being added. It takes years to learn every corner of the language.

But you do not need to learn all of it. Effective use of C++ requires only the knowledge of a couple of its essential features. In this post, I am going to write about one C++ feature that I consider one of the most important. The one that makes me choose C++ rather than other popular programming languages.

Determined object life-time

Each object you create in your program has a precisely determined life-time. Once you decide on a particular life-time, you know exactly at which point your object’s life-time starts, and when it ends.

For automatic variables, its life-time starts upon its declaration, after the initialization finishes normally (not via an exception); its-life time ends upon leaving the scope it has been declared in. For two automatic objects declared next to each other, the one whose life-time has begun earlier, its life-time also ends later.

For function parameters, their life-time begins just before the function execution starts, and their life-time ends just after the function is executed.

For globals (variables defined in namespace scope), their life-time starts before main starts, and their life-time ends after main ends. For two globals defined in the same translation unit (file, after header inclusion), the one define higher starts life earlier and ends later. For two globals defined in different translation units, no assumptions about their relative life-times can be made.

For almost all temporaries (with two well-defined exceptions), their life-time begins when a function returns by value inside a bigger expression (or when they are created explicitly), and ends after the full expression has been evaluated.

The two exceptions are: when a temporary is bound to a global or automatic reference, its life time lasts as long as that of the reference’s:

Base && b = Derived{}; int main() { // ... b.modify(); // ... } // life-time of temporary Derived ends here

(Note that the reference is of different type (base class of) than a temporary, and that we can modify the temporary. ‘Temporary’ typically means ‘short-lived’, but when bound to a global reference it will live as long as any other global in the program.)

Second exception to the rule is applicable when we initialize a built-in array of user-defined types . In that case, if a default constructor is used to initialize n -th array element, and that default constructor has one or more default arguments, the life-time of each temporary created in a default argument ends when we proceed to initializing ( n + 1)-th element. But you will probably never need to know this one.

For member sub-objects of classes, their life-time begins when their mother’s life-time starts and their life-time ends when the mother’s life-time ends.

Similarly for other kinds of object life-time: function-local statics, thread-locals, or when we manually control the life time of an object, for instance with new / delete or with allocators or with optional or some such. In all these cases, it is well-defined and predictable where exactly the life-time of an object starts and when its life-time ends.

In case an initialization of an object, whose life-time is about to start, fails (an exception is thrown), its life-time does not start.

This is, in short, the nature of a deterministic object life-time. So, what is an indeterministic one? You do not have it in C++ (yet), but you can see it in other languages, with ‘garbage collection’ support. In such cases, you create an object — its life time starts — but you do not know when its life-time will end. You are guaranteed that it will not end until you still hold a reference to it, but once you drop the last reference to it, it can live arbitrarily long, including up to the termination of the process.

So, what is so important about the deterministic object life-time?

The destructor

C++ guarantees that for any object of class type, its destructor will be called the moment the object’s life-time ends. A destructor is a member function in our object’s class: guaranteed to be the very last function that will be called for our object.

Everyone knows that already, but not everyone appreciates what it really buys us. First, and most importantly, you can use the destructor to clean up the resources that our object acquired throughout its life-time. This clean-up becomes encapsulated: hidden from the user: you (the user) do not have to call any dispose or close function. There is no way you can forget to clean the resources up. You do not even need to know if this type you are currently using (especially inside a template, where it is just a T for you) manages resources or not. Also, the object’s resources are cleaned up immediately when the object is no longer needed: not in some indeterminate future. Resources are released as soon as possible. This prevents resource leaks. No garbage is left, blocking resources for unpredictable time. (Do not think about memory, when you read ‘resource’: think about open DB connections or sockets.)

The deterministic object life-time also determines the relative destruction order of the objects. In case there are a number of automatic objects declared in scope they are destroyed in the inverse order of their declaration (and initialization). Similarly for class member sub-objects: they are destroyed in the inverse order of their declaration in class body (and initialization). This is essential in case the resources depend on one another.

This feature is superior to garbage collection in the following aspects:

It provides a uniform approach to all resources one can think of — not only memory. Resources are released immediately after they are no longer used. Not when (if ever) garbage collector decides to clean memory (and call the finalizers). It does not require the run-time overhead of garbage collector.

Garbage collector-based languages tend to offer substitutes for resource-handling techniques: using statement in C# or try-with-resources statement in Java. While they are step in good direction, they are still inferior to the usage of destructor.

Resource management is exposed to the user: now you need to know that the type you are using manages a resource and put some extra code to request the resource release. You may forget to put the guard statement and then the resource is leaked. If class maintainer decides to change the implementation from one that doesn’t manage a resource to one that does, the user has to change her code also: this is the consequence of resource disposal not being encapsulated. This doesn’t work well with generic programming: you cannot write the code that uniformly operates on types that do and do not use resources.

Finally (pun intended), such guarding statements are only a substitute for the cases handled by C++ ‘automatic’ objects, created in function or block scopes. C++ provides other kinds of object life-time. For instance, you can make a resource-managing object a member of another ‘master’ object and thereby say that the resource must be held as long as the master object lives.

Imagine the following function that opens n file streams and returns them in a collection. Another function reads them and automatically closes:

vector<ifstream> produce() { vector<ifstream> ans; for (int i = 0; i < 10; ++i) { ans.emplace_back(name(i)); } return ans; } void consumer() { vector<ifstream> files = produce(); for (ifstream& f: files) { read(f); } } // close all files

How do you do that with ‘using’ or ‘try-with-resources’ statement?

Note a certain trick here. We make use of another important C++ feature: move constructor. We use the fact that std::fstream is not copyable, but is moveable. (However, users of GCC 4.8.1 will not notice this.) The same — transitively — goes for std::vector<std::ifstream> . The move operation is like an emulation of yet another, unique, object life-time. We have a ‘virtual’ or ‘artificial’ life-time of a resource (a collection of file handles) that starts with the life-time of object ans and ends with the life-time of a different object defined in a different scope: files .

Note that throughout the ‘extended’ life-time of the collection of file handles, each handle is protected against being leaked in case an exception is thrown. Even if function name throws in the 5th iteration, all previously created 4 elements are guaranteed to be released in function produce .

Similarly, what you cannot do with ‘guarding’ statements is this:



class CombinedResource { std::fstream f; Socket s; CombinedResource(string name, unsigned port) : f{name}, s{port} {} // no explicit destructor };



This already gives you a couple of useful resource safety guarantees. Your two resources will be released when CombinedResource ’s life-time ends: this will be done in the implicitly defined destructor, in the inverse order of initialization — you do not have to type anything. In case the initialization of the second resource, s , fails in the constructor (ends in exception), the destructor of the already initialized f will immediately be called, before the exception is propagated from our constructor. You get all these guarantees for free.

How do you do that with ‘using’ or ‘try-with-resources’ statement?



The dark sides

It is fair to mention here, that there are reasons people do not like destructors for. There are situations where garbage collector is superior to ‘no garbage’ solution offered by C++. For instance, with garbage collector (if you can afford using one), you can nicely represent a cyclic graph, by just allocating nodes and connecting them with pointers (or ‘references’ if you will). In C++ it will not work even with ‘smart’ pointers. Of course, nodes in such garbage-collected graphs cannot manage resources, because they might be leaked: ‘using’/’try-with-resources’ statements cannot help there, and finalizers may not be guaranteed to be called.

Also, I heard people say that there exist efficient concurrent algorithms that can only work with garbage-collector’s assistance. I admit I have never seen one.

Some people dislike the fact that you cannot see the destructor call in the code. What is an advantage to some, discourages others. When you analyse or debug the code, it can slip your attention that destructor is called and can have some side effects. I did fell into such trap when debugging a big, entangled piece of code once. An object under the raw pointer I was holding suddenly would become rubbish for an unknown reason: I could see no function that would cause this. Only later did I realize that the same object was referred to by another unique_ptr , which silently went out of scope. For temporary objects it may be even worse: you can see neither the destructor, nor the object itself.

There is one restriction on using destructors: in order for them to correctly interact with stack unwinding (caused by exceptions), they must not emit exceptions themselves. This restriction can prove very hard for people who need to signal resource release failure, or who use destructors for other purposes.

Note that in C++11, unless you declare your destructor as noexcept(false) , it is implicitly declared as noexcept and will call std::terminate if an exception is emitted from it. In case you want to signal resource release failure with exception, the recommendation is that your type should also provide a member function like release which users should call explicitly, and then have the destructor check if you released the resource already, and if not, try to release it ‘silently’ (swallowing any exceptions).



One other potential downside connected with the usage of destructor for resource release is that sometimes you need to introduce an additional, somewhat artificial scope/block inside your function only to trigger the destructor call of an automatic object, long before the function’s scope ends. Consider:

void Type::fun() { doSomeProcessing1(); { std::lock_guard<std::mutex> g{mutex_}; read(sharedData_); } doSomeProcessing2(); }

Here, we had to put an additional scope, so that the mutex is not locked while we are performing doSomeProcessing2 : we want to release our resource (the mutex) as soon as we stop using it. This now looks somewhat similar to ‘using’/’try-with-resources’ statement, but there are two differences:

This is an exception rather than the rule. If we forget about the scope, the resource is held for too long, but not leaked — because the destructor is still bound to be called.

And that’s it. Personally, I find the destructor one of the most elegant and practical features in programming languages. And note that I didn’t start explaining its other strengths: interaction with exception handling mechanism. This is what attracts me to C++ far more than performance: the elegance.

And one final note: I did not want to make a claim that this feature is really the best one in the language. I just wanted the title to be catchy.