Delegating constructors are one of those minor language features that don’t get a lot of headlines, but make a programmer’s life much easier. It’s not likely something you’ll use on a daily basis, but it is something you will run across eventually.
Picking a real-world use case: let’s say you have a logging class which has three different constructors. Two constructors take a path as their parameter (one with a naked string pointer, another with a FilePath class), and the third constructor is the default constructor which places the logging file in a well-known location.
In a naive implementation, your constructors might look something like this:
Logger::Logger( const wchar_t *filePath ) : mFile( NULL ) { mFile = ::wfopen( filePath, L"at" ); } Logger::Logger( const FilePath& filePath ) : mFile( NULL ) { mFile = ::wfopen( filePath.GetAbsolutePath(), L"at" ); } Logger::Logger() : mFile( NULL ) { mFile = ::wfopen( L"C:\\Temp\\log.txt", L"at" ); }
You’ll notice that this is a whole lot of duplicate code! What if we wanted to overwrite the file if it exists instead of simply appending to it? Then we have to change all three constructor methods to do so. What if we want to throw an exception if the file cannot be created? Again, modify all three methods. We can certainly do better.
In C++03 and earlier, the better way to implement this would be to delegate the functionality to a helper method, and then call that helper from the constructor. It might look something like this:
void Logger:Init( const wchar_t *filePath ) { mFile = ::wfopen( filePath, L"at" ); } Logger::Logger( const wchar_t *filePath ) : mFile( NULL ) { Init( filePath ); } Logger::Logger( const FilePath& filePath ) : mFile( NULL ) { Init( filePath.GetAbsolutePath() ); } Logger::Logger() : mFile( NULL ) { Init( L"C:\\Temp\\log.txt" ); }
Now there’s less duplicate code because the Init function handles all of the work, and everyone else calls through to it. However, with C++0x, there’s an even better way. We can get rid of the Init function entirely, and simply use delegating constructors.
Logger::Logger( const wchar_t *filePath ) : mFile( NULL ) { mFile = ::wfopen( filePath, L"at" ); } Logger::Logger( const FilePath& filePath ) : Logger( filePath.GetAbsolutePath() ) { } Logger::Logger() : Logger( L"C:\\Temp\\log.txt" ) { }
Now there’s only one constructor which is non-trivial, and the rest simply delegate their functionality to the non-trivial constructor. You still have only one place to make your modifications instead of three, and you don’t require a helper method to do so. This has an extra benefit as well, in that you don’t need to have three sets of initializer lists that initialize all of the class member variables. Instead, you can have one master initializer list, and the other constructors can simply delegate initialization.
Construction still happens as normal with delegated constructors. Let’s say the user calls the default Logger constructor. First, the initializer list for Logger() is fired, and that delegates the call to Logger( const wchar_t * ). So its initializer list is called, and mFile is set to NULL. Then the constructor method is called, and mFile is assigned into. Then the constructor method for Logger() is called. So you can delegate partial responsibility to another constructor method, but still have specialization within the originating constructor method. For example:
class Test { public: Test() { ::printf( "Test::Test called\n" ); } Test( int i ) : Test() { ::printf( "Test::Test(int) called\n" ); } };
If you constructed a Test object by “Test t( 42 )”, you would see “Test::Test called” and then “Test::Test(int) called” as the output.
Unfortunately, Visual Studio 2010 and XCode 4 (gcc 4.2 and Clang 2.0) do not support delegating constructors yet. However, I will certainly rejoice when support arrives for this feature because I know of several classes I will be refactoring to take advantage of the clarity this functionality provides.
tl;dr: instead of using helper methods or duplicating code, you can chain constructor methods together in C++0x.