When to be Explicit

In C++, a constructor that accepts a single parameter non-defaulted parameter is also considered a converting constructor. Converting constructors allow you to initialize a class instance using that single parameter type either via explicit construction, or via an assignment construction. For instance:

class Foo {
public:
	Foo( int i );
	Foo( const char *s, bool b = false );
};

Foo f = 12;  // Calls Foo( int )
Foo f2 = "Aaron";  // Calls Foo( const char *, bool )

This is a handy feature for some class types, but can be entirely incorrect for other class types. The purpose of this post is to explore the problems of assignment construction and how to overcome them.

The first thing to realize is that assignment is more than just the equals sign. For instance, there are implicit assignments that happen at call sites for copying parameters to a called function. This can lead to problems with converting constructors being called when the user didn’t actually mean it. Using our class declaration from above:

void blah( Foo f );

blah( 12 );  // blah( Foo( 12 ) );
blah( "hahahaha" ); // blah( Foo( "hahahaha", false ) );
blah( Foo( 42 ) ); // Behaves how you'd expect

It’s debatable as to whether this is a good behavior or a bad behavior. If the Foo class is meant to represent a basic object such as a string or number type, then it may be desired that the converting constructor is called. But if the Foo class is a more complex type, then it’s possible that the caller has made mistake at the call site and this should produce an error.

A more real-world example would be a case where some implicit conversions are fine, and some are not fine. For instance, with a string class, you might have the following constructors:

MyString( const char *s );
MyString( size_t bufferSize );

In this case, the call with the const char * would likely make sense to use the converting constructor because the class represents a string. However, the constructor accepting a buffer size should not be using implicit construction because it is expected to be used far less often and more likely to cause harm than good. For instance:

void Foo( MyString s );

Foo( "Yay!" );  // MyString( const char * )
Foo( 'x' );  // MyString( size_t )
Foo( 12 );  // MyString( size_t )

The latter two examples show that the caller probably did not expect to create a string with a buffer but no contents.

Now that you’ve seen some examples of why implicit converting constructors are harmful, it’s time to learn how to combat the issue: by being explicit. C++ has the explicit keyword, and it is meant to be used on converting constructors to prevent the compiler from calling them in implicit conversion situations. You can still call the converting constructor explicitly, or via a cast operation. But the basic assignment cases will produce a compile error. The way we would correct the MyString constructor accepting a buffer size is:

explicit MyString( size_t bufferSize );

Now, if the user writes code that would call the explicit converting constructor they will get an error. From our previous example, the call with the const char * will succeed, but the character literal and number will both fail with an error like: “Cannot convert from int to MyString.”

The rule of thumb I tend to follow is: make all of your single-parameter constructors explicit by default. Then remove the explicit keyword only when you decide that a particular constructor is more clear with implicit construction. Since the default behavior of C++ is to allow implicit conversion constructor calls, this pattern makes your code safer while still allowing you to create more intuitive interfaces as needed.

tl;dr: C++98 supports the explicit keyword for converting constructors. You should always make your single-parameter constructors explicit unless there’s good reason not to.

This entry was posted in C/C++ and tagged , . Bookmark the permalink.

2 Responses to When to be Explicit

  1. Dan says:

    Aaron,

    I’m glad that you mentioned the single “non default” parameter – a lot of folks are under the impression that the only type of converting constructor is one that has only one parameter, period. (Similar to how any constructor with all default parameters can act as a default constructor — many people think “default constructor” implies “no parameters.)

    Regarding making converting constructors “explicit” by default, I totally agree. I’ve heard Dan Saks (who was on the C++ Standards Committee) say multiple times that “the C++ standards committee got it backwards – converting constructors should be explicit by default, and you should have to go out of your way to change this default behavior.” Hindsight!

    One thing I’ve done for certain projects that make use of a good coding standard & code reviews:

    #define IMPLICIT
    #define EXPLICIT explicit

    and require (via coding standards and inspection) that every converting constructor use one of these 2. We even had a Perl guy write a utility that would scan every class definition, find the converting constructors, and flag the ones that weren’t marked as IMPLICIT or EXPLICIT.

  2. Aaron Ballman says:

    That’s a good idea for a coding convention, and I especially like the use of Perl to enforce it! I totally agree that the standards committee got it backwards. But it’s nice to see them making amends with some of the other new language features.

Leave a Reply

Your email address will not be published.