In this post we will talk about one interesting feature in C ++ 11, which is called delegating constructors: why is it interesting, and how can it be used for more efficient resource management, i.e. implementation of the RAII idiom.
Briefly about RAII (well, very briefly)
When we need to automate the management of some “bare” resource, we “wrap” it in a separate class. Let us demonstrate this with an example of such a resource as FILE from the standard C library:
')
#include <stdio.h> class File { public: File(char const * filename, char const * mode) : file_(fopen(filename, mode)) {} ~File() { fclose(file_); } File(File const &) = delete; File operator=(File const &) = delete; // file operations // ... private: FILE * file_; };
Here we create a FILE resource in the constructor and release it in the destructor. Now the FILE resource is managed in full accordance with the RAII idiom.
A slightly more complicated case of RAII
Suppose now that in addition to opening a file in the constructor, we need to perform some operation with it. For example, we will record the time of the last opening, time stamp, in the newly opened file. To do this, create a function in the File class put_time_stamp, which in some way puts the time stamp in the file, and in case of failure throws some kind of exception.
This case is implemented somehow like this:
#include <stdio.h> class File { public: File(char const * filename, char const * mode) : file_(fopen(filename, mode)) { put_time_stamp(); } ~File() { fclose(file_); } File(File const &) = delete; File operator=(File const &) = delete; // file operations void put_time_stamp() { // throws on error // ... } private: FILE * file_; };
But as you can see, there is a slight problem with this implementation. The File constructor is no longer safe. If an exception is thrown from put_time_stamp, it will not result in calling the File object destructor, since its constructor has not yet completed. Therefore, the file_ resource will be lost.
How do we solve this problem? The stupid “head-on” solution is to wrap the put_time_stamp call into a try / catch block:
class File { public: File(char const * filename, char const * mode) try : file_(fopen(filename, mode)) { put_time_stamp(); } catch (...) { destruct_obj(); } ~File() { destruct_obj(); } private: void destruct_obj() { fclose(file_); } FILE * file_; };
This approach works, but it is a bit ugly because of the need to have an explicit try / catch block and a separate method for explicitly destroying the object so as not to duplicate the same functionality in the catch block and in the destructor.
We can slightly improve this solution if we introduce an additional class specifically for storing and removing FILE, FileHandle:
class File { struct FileHandle { FileHandle(FILE * fh) : fh_(fh) {} ~FileHandle() { fclose(fh_); } FILE * fh_; } public: File(char const * filename, char const * mode) : file_(fopen(filename, mode)) { put_time_stamp(); } ~File() = default; private: FileHandle file_; };
As you can see, now the explicit try / catch block is no longer needed. The file_ object will be correctly destroyed even if an exception is thrown from the constructor of the File class and the FILE resource is freed. But this solution still has some flaw in the separate FileHandle class, which spreads the creation and release of the FILE resource into two different classes: FILE is created in the File class, and released in the FileHandle class.
Delegating constructors
Now let's consider one very useful feature from C ++ 11 called delegating constructors, which will allow us to further improve the previous code of the File class. But first, let's see how these delegating constructors work at all.
Suppose we have a class with two constructors: one of the parameter of type int, and the other of double. The constructor for int does the same thing as the constructor for double, but first it translates the parameter from type int to type double. Those. the constructor for int delegates the creation of an object to the constructor for double. Here is how it looks in code:
class MyClass { public: MyClass(double param) {
After the constructor for double finishes executing, the constructor for int can continue to execute and “unfinish” the object. This in itself is a very useful feature, without which in the code above we probably would have to introduce an additional function init (double param) to encapsulate the common code to create an object of type double.
But in addition this feature has one very interesting side effect. The fact is that as soon as one of the object constructors finishes execution, the object is considered to be created. And it means that if another constructor, from which the delegating call of the first constructor originated, ends up with throwing an exception, a destructor will still be called for this object. Note the critical point: for the object, more than one constructor can now be executed. But the object is considered to be created after the execution of the very first constructor.
Let us demonstrate this behavior with the following example:
class MyClass { public: MyClass(double) { cout << "ctor(double)\n"; } MyClass(int val) : MyClass(double(val)) { cout << "ctor(int)\n"; throw "oops!"; } ~MyClass() { cout << "dtor\n"; } }; int main() try { MyClass obj(10); cout << "obj created"; } catch (...) { cout << "exception\n"; }
The constructor MyClass (int) calls another constructor MyClass (double), after which it throws an exception. This exception is caught in catch (...), and when the stack is expanded, the ~ MyClass destructor is called. When executing this code, the following will be displayed on the console:
ctor(double) ctor(int) dtor exception
Delegating Designers and RAII
It is easy to see that such interesting behavior of constructors during delegation can be used very effectively in our example of implementing RAII for FILE. Now we don’t need to introduce any additional FileHandle class to free the FILE resource, and all the more we don’t need try / catch. You need to enter only one additional constructor, which will be made a delegation from the main designer. I.e:
class File { File(FILE * file) : file_(file) {} public: File(char const * filename, char const * mode) : File(fopen(filename, mode)) { put_time_stamp(); } ~File() { fclose(file_); } void put_time_stamp() { ... } private: FILE * file_; };
And that's all we need. Very nice, elegant and completely safe with respect to exceptions (exception safe). Conclusion: such a technique will greatly facilitate the implementation of the RAII idiom in the new code using delegating constructors from C ++ 11.