Why You Should Always Use explicit Constructors
Part of the overloading rules in C++ allow for types to be freely converted to other types through the use of single-argument constructors. Since no compilers currently inform you when this is going on it can lead to obscure bugs and/or inefficient code.
This behaviour can be controlled through the C++ keyword explicit, however, in my experience even some very experienced C++ developers won’t be able to tell you how this keyword is used. This article is meant to be a simple description of explicit, and why it should be applied in virtually all cases.
A Trip Into Overload-Ville
Consider the following code fragment:
// A simple class
class A {};
// Another simple class with a single-argument constructor for class A
class B
{
public:
B() {}
B(A const&) {}
};
// A function that expects a 'B'
void f(B const&) {}
int main()
{
A obj;
f(obj); // Spot the deliberate mistake
}
Even though A and B are unrelated types, the presence of a single-argument constructor allows the compiler to silently construct a temporary instance of class B, effectively generating the code:
int main()
{
A obj;
{
B obj_arg0(obj); // A temporary for the lifetime of the call
f(obj_arg0);
}
}
In this particular example, the effect of this conversion is negligible, however consider the case where the construction of B from A requires a lengthy conversion process, or (worse) the case where the function f stores some data from its argument. This should be entirely legal since the argument is passed by reference, but since the compiler decided to construct a temporary, the argument goes out of scope after the function has been called, invalidating the stored data. This sort of situation can lead to nightmarish bugs, since no compiler warnings will ever be generated from the conversion.
Telling Your Compiler To Calm Right Down
This is where the explicit
keyword comes in. If we adjust the constructor of B we obtain the following code:
class A {};
class B
{
public:
B() {}
explicit B(A const&) {} // Note the keyword 'explicit' here
};
void f(B const&) {}
int main()
{
A obj;
f(obj);
}
Now when we try to compile this file your compiler should tell you something like this:
test.cpp: In function `int main()':
test.cpp:15: error: could not convert `obj' to `const B&'
test.cpp:10: error: in passing argument 1 of `void f(const B&)'
This time the compiler can’t do the conversion since we told it not to use that constructor unless we do explicitly. If we actually want the conversion we can explicitly call the constructor as follows:
int main()
{
A obj;
f(B(obj)); // Clearly using a temporary for the lifetime of the function
}
Conclusion
Nobody can write code without making the odd typo. If you’re unlucky and make a typo that the compiler won’t catch then you’re in trouble. Restricting the behaviour of the compiler so that it catches code fragments that may do what you don’t expect can only be a good thing, and once the compiler has caught the use of an explicit constructor it is trivial to construct the desired type explicitly. Also, anyone who comes to maintain that code can instantly see the conversion being done without having to manually check parameter types against the function declaration.
For these reasons I’d suggest that all single-argument constructors should be made explicit
.
Gotchas
Bear in mind the compiler can treat constructors with default values as single-argument, and therefor perform type conversions with them:
class C
{
public:
explicit C(B const&, A const& a = A());
};
Even though the constructor can take two arguments, explicit is still necessary to catch the single-argument case.