
I would like to start by extending an apology to You-Know-Who-You-Are :). Even though your coding style prompted me to revisit it, I have been pondering this issue for some time now. It was only when I went back to the text books to read up on it that I finally understood how to describe this in accessible terms. Take solace in the fact that the “you” above is the plural form :).
Over the years, I have repeatedly come across colleagues that are obviously familiar with the Sutter book “More Exceptional C++” and its Item 36 (or his internet column, Guru of the Week #1), which contains the guideline
Prefer using the form “T t(u)” over “T t = u” for variable initialisation.
While I myself try to follow Sutter/Alexandrescu/Meyers in my everyday coding, I’ve always been slightly uncomfortable with a few of their guidelines, such as the suggestion to add virtual
in front of reimplemented virtuals. That particular issue is fodder for another blog post. Today, I’d like to talk about why I never follow the guideline to prefer direct over copy initialisation, unless I have to use direct initialisation.
Direct Initialisation, of course, is the construction of variables by calling one of their constructors (T t(u)
), while Copy Initialisation is construction of variables by first constructing a temporary of the variable’s type, and then copy-constructing the variable from that temporary (T t = u
, which is equivalent to T t( T(u) )
).
At face value, direct initialisation is preferable, because it avoids the extra copy constructor. However, the standard allows compilers to elide the copy constructor call (the copy constructor still has to be accessible, though), and every half-decent compiler will implement that optimisation, thus making the two all but identical at runtime.
So, if avoiding premature pessimisation isn’t the reason to prefer direct initialisation, what is? Interestingly, there appear to be no reasons (left anymore). While the original GotW cited “works in more cases” as a reason, the corresponding MEC++ Item #36, written a few years later, does not give any rationale for the guideline anymore. Even more interestingly, The Good Book, written yet some time later, doesn’t even include that guideline! Clearly, something’s wrong with that guideline. Don’t you, too, sometimes wish the Good Book had an appendix on “items that didn’t make it, and why?” :).
As far as I have made out, there are three main reasons not to prefer to use direct initialisation, two objective, and one highly subjective.
Let’s start with the subjective one, so you end up remembering the objective ones better 🙂
It just looks plain weird.
Especially for people coming from C, which has no syntax for direct initialisation.
Ok, with the subjective reason out-of-the-way, let’s look at the two objective reasons:
First, direct initialisation sometimes ends up looking like a declaration, and the standard requires that everything that looks like a declaration is also parsed as one. That’s the issue behind C++’ most vexing parse (coined in Meyers: Effective STL, Item 6):
const U u; // ok, defines a variable u of type U, and default-initialises it
const T t(u); // ok, defines a variable t of type T, initialised from u
const T t(U()); // oops, declares a function t, taking a function returning U as argument and returning const T!
Second, direct initialisation disables the protection provided by explicit
constructors. Consider the classical mistake that explicit
constructors are to prevent you from making:
class Stack {
public:
explicit Stack( size_t maxSize );
// ...
};
Stack * stack = 0; // ok
// same line, but forgot the '*'
Stack stack = 0; // error: Stack(size_t) is explicit -> good
// same line, now with direct initialisation:
Stack stack(0); // oops, compiles!
In other words, copy construction, by virtue of potentially involving an implicit call to a conversion constructor (remember that T t = u
is really T t( T(u) )
), cannot be used when the conversion constructor is explicit
. Direct initialisation, on the other hand, by making an explicit call to the conversion constructor (T t(u)
), will succeed whether or not the constructor is explicit
.
So, if you always prefer direct initialisation over copy initialisation, you’re more likely to hit “C++’s most vexing parse”, as well as suffering from unintentional use of explicit
constructors. Don’t go there. Prefer copy construction.
That said, there are, of course, situations where you should use direct initialisation. E.g. when calling a constructor with more than one argument:
// this is overly verbose:
const QDateTime dt = QDateTime( 2010, 8, 16 );
// better:
const QDateTime dt( 2010, 8, 16 );
Likewise, when you want to call an explicit
constructor, you have to use direct initialisation, too:
std::vector<std::string> v( 10 );
If you default to using copy initialisation, direct initialisation stands out in your code, and marks places where something potentially dangerous, or expensive happens.