Dear Editor,
I really enjoyed Cassio Neri’s article on ‘Complex Logic in the Member Initialiser List’ as it’s a problem I’ve had to deal with several times. I was surprised to learn that Listing 6 was valid, because I didn’t realise callers of that constructor didn’t need access to the private ‘storage’ type to create the default argument at the call site. On reflection I realised the caller doesn’t have to name the type and that access checking in C++ is done on names. I love an Overload article that makes me stop halfway through reading to reach for the compiler and learn something new!
Despite discussing how some C++11 features help solve the problems being discussed, I was surprised to notice the article didn’t mention one of the new C++11 features that I find very useful for solving exactly those sort of problems. The ‘delegating constructors’ feature allows one constructor to invoke a different constructor, in order to avoid duplicating logic in constructor bodies. This feature is useful when one constructor (the delegating constructor) wants to process or munge its arguments somehow and then pass them on to another constructor (the target constructor.) Using delegating constructors allows Cassio’s Listing 6 to be rewritten like so:
class bar : public base { struct storage { storage(double d) : b(d * d) { } double b; }; ... bar(double d, foo& r1, foo& r2, storage tmp); public: bar(double d, foo& r1, foo& r2); }; bar::bar(double d, foo& r1, foo& r2) : bar(d, r1, r2, storage(d)) { } bar::bar(double d, foo& r1, foo& r2, storage tmp) : base(tmp.b), x_(cos(tmp.b)), y_(sin(tmp.b)), ... { }
In this version of the code the user-accessible constructor doesn’t mention the private ‘storage’ type, which separates the interface meant for users from the implementation details of the complex constructor logic. Additionally, I believe it solves several of the problems mentioned in the article and makes the techniques shown in Listing 7 and Listing 8 unnecessary, and ultimately avoids the need to use a discriminated union for this scenario.
Because the temporary ‘storage’ object is initialized in the member initializer list, not the parameter list, there is no restriction on referring to the function parameters, so ‘storage’ can be constructed with ‘d’ and can have an arbitrarily complex constructor that can do any necessary calculations. Because the delegating constructor doesn’t initialize any base classes (that’s done by the target constructor) the ‘storage’ object is guaranteed to be initialized before the target constructor is invoked so it avoids any problems with order of initialization of base classes.
I think using delegating constructors for this problem is almost the ideal solution. The ‘storage’ constructor allows the complex logic to be placed in a separate function where it can be written more naturally (rather than in a contrived function such as
bar::init_base
) and that function is guaranteed to be executed at exactly the right time: after the user calls the (delegating) constructor but before the invocation of the target constructor that actually performs initialization of the base classes and members.
The only downside, which might be why they weren’t considered in the original article, is that delegating constructors were not widely supported until quite recently. Clang has supported them since version 3.0, GCC since 4.7 and MSVC now supports them too as of the November 2012 CTP.
Yours,
Jonathan Wakely
Dear Jonathan,
Thank you for bringing this to our attention. Feel free to write in with any further comments. Cassio tells me Jeff Snyder (who can be found online as je4d) contacted him with similar remarks about delegating constructors.
Frances – Overload editor