Item 5: Be wary of user-defined conversion functions.
C++ allows compilers to perform implicit conversions between types. In honor of its C heritage, for example, the language allows silent conversions from char
to int
and from short
to double
. This is why you can pass a short
to a function that expects a double
and still have the call succeed. The more frightening conversions in C those that may lose information are also present in C++, including conversion of int
to short
and double
to (of all things) char
.
You can't do anything about such conversions, because they're hard-coded into the language. When you add your own types, however, you have more control, because you can choose whether to provide the functions compilers are allowed to use for implicit type
Two kinds of functions allow compilers to perform such conversions: single-argument constructors and implicit type conversion operators. A single-argument constructor is a constructor that may be called with only one argument. Such a constructor may declare a single parameter or it may declare multiple parameters, with each parameter after the first having a default value. Here are two examples:
class Name { // for names of things public: Name(const string& s); // converts string to // Name ... }; class Rational { // for rational numbers public: Rational(int numerator = 0, // converts int to int denominator = 1); // Rational ... };
An implicit type conversion operator is simply a member function with a strange-looking name: the word operator
followed by a type specification. You aren't allowed to specify a type for the function's return value, because the type of the return value is basically just the name of the function. For example, to allow Rational
objects to be implicitly converted to double
s (which might be useful for mixed-mode arithmetic involving Rational
objects), you might define class Rational
like
class Rational { public: ... operator double() const; // converts Rational to }; // double
This function would be automatically invoked in contexts like
Rational r(1, 2); // r has the value 1/2 double d = 0.5 * r; // converts r to a double, // then does multiplication
Perhaps all this is review. That's fine, because what I really want to explain is why you usually don't want to provide type conversion functions of any
The fundamental problem is that such functions often end up being called when you neither want nor expect them to be. The result can be incorrect and unintuitive program behavior that is maddeningly difficult to
Let us deal first with implicit type conversion operators, as they are the easiest case to handle. Suppose you have a class for rational numbers similar to the one above, and you'd like to print Rational
objects as if they were a built-in type. That is, you'd like to be able to do
Rational r(1, 2); cout << r; // should print "1/2"
Further suppose you forgot to write an operator<<
for Rational
objects. You would probably expect that the attempt to print r
would fail, because there is no appropriate operator<<
to call. You would be mistaken. Your compilers, faced with a call to a function called operator<<
that takes a Rational
, would find that no such function existed, but they would then try to find an acceptable sequence of implicit type conversions they could apply to make the call succeed. The rules defining which sequences of conversions are acceptable are complicated, but in this case your compilers would discover they could make the call succeed by implicitly converting r
to a double
by calling Rational
::operator
double
. The result of the code above would be to print r
as a floating point number, not as a rational number. This is hardly a disaster, but it demonstrates the disadvantage of implicit type conversion operators: their presence can lead to the wrong function being called (i.e., one other than the one
The solution is to replace the operators with equivalent functions that don't have the syntactically magic names. For example, to allow conversion of a Rational
object to a double
, replace operator
double
with a function called something like asDouble
:
class Rational { public: ... double asDouble() const; // converts Rational }; // to double
Such a member function must be called
Rational r(1, 2); cout << r; // error! No operator<< // for Rationals cout << r.asDouble(); // fine, prints r as a // double
In most cases, the inconvenience of having to call conversion functions explicitly is more than compensated for by the fact that unintended functions can no longer be silently invoked. In general, the more experience C++ programmers have, the more likely they are to eschew type conversion operators. The members of string
type they added to the library contains no implicit conversion from a string
object to a C-style char*
. Instead, there's an explicit member function, c_str
, that performs that conversion. Coincidence? I think
Implicit conversions via single-argument constructors are more difficult to eliminate. Furthermore, the problems these functions cause are in many cases worse than those arising from implicit type conversion
As an example, consider a class template for array objects. These arrays allow clients to specify upper and lower index
template<class T> class Array { public: Array(int lowBound, int highBound); Array(int size); T& operator[](int index); ... };
The first constructor in the class allows clients to specify a range of array indices, for example, from 10 to 20. As a two-argument constructor, this function is ineligible for use as a type-conversion function. The second constructor, which allows clients to define Array
objects by specifying only the number of elements in the array (in a manner similar to that used with built-in arrays), is different. It can be used as a type conversion function, and that can lead to endless
For example, consider a template specialization for comparing Array<int>
objects and some code that uses such
bool operator==( const Array<int>& lhs, const Array<int>& rhs); Array<int> a(10); Array<int> b(10); ... for (int i = 0; i < 10; ++i) if (a == b[i]) { // oops! "a" should be "a[i]" do something for when a[i] and b[i] are equal; } else { do something for when they're not; }
We intended to compare each element of a
to the corresponding element in b
, but we accidentally omitted the subscripting syntax when we typed a
. Certainly we expect this to elicit all manner of unpleasant commentary from our compilers, but they will complain not at all. That's because they see a call to operator==
with arguments of type Array<int>
(for a
) and int
(for b[i]
), and though there is no operator==
function taking those types, our compilers notice they can convert the int
into an Array<int>
object by calling the Array<int>
constructor that takes a single int
as an argument. This they proceed to do, thus generating code for a program we never meant to write, one that looks like
for (int i = 0; i < 10; ++i) if (a == static_cast< Array<int> >(b[i])) ...
Each iteration through the loop thus compares the contents of a
with the contents of a temporary array of size b[i]
(whose contents are presumably undefined). Not only is this unlikely to behave in a satisfactory manner, it is also tremendously inefficient, because each time through the loop we both create and destroy a temporary Array<int>
object (see Item 19).
The drawbacks to implicit type conversion operators can be avoided by simply failing to declare the operators, but single-argument constructors cannot be so easily waved away. After all, you may really want to offer single-argument constructors to your clients. At the same time, you may wish to prevent compilers from calling such constructors indiscriminately. Fortunately, there is a way to have it all. In fact, there are two ways: the easy way and the way you'll have to use if your compilers don't yet support the easy
The easy way is to avail yourself of one of the newest C++ features, the explicit
keyword. This feature was introduced specifically to address the problem of implicit type conversion, and its use is about as straightforward as can be. Constructors can be declared explicit
, and if they are, compilers are prohibited from invoking them for purposes of implicit type conversion. Explicit conversions are still legal,
template<class T> class Array { public: ... explicit Array(int size); // note use of "explicit" ... }; Array<int> a(10); // okay, explicit ctors can // be used as usual for // object construction Array<int> b(10); // also okay if (a == b[i]) ... // error! no way to // implicitly convert // int to Array<int> if (a == Array<int>(b[i])) ... // okay, the conversion // from int to Array<int> is // explicit (but the logic of // the code is suspect) if (a == static_cast< Array<int> >(b[i])) ... // equally okay, equally // suspect if (a == (Array<int>)b[i]) ... // C-style casts are also // okay, but the logic of // the code is still suspect
In the example using static_cast
(see Item 2), the space separating the two ">
" characters is no accident. If the statement were written like
if (a == static_cast<Array<int>>(b[i])) ...
it would have a different meaning. That's because C++ compilers parse ">>
" as a single token. Without a space between the ">
" characters, the statement would generate a syntax
If your compilers don't yet support explicit
, you'll have to fall back on home-grown methods for preventing the use of single-argument constructors as implicit type conversion functions. Such methods are obvious only after you've seen
I mentioned earlier that there are complicated rules governing which sequences of implicit type conversions are legitimate and which are not. One of those rules is that no sequence of conversions is allowed to contain more than one user-defined conversion (i.e., a call to a single-argument constructor or an implicit type conversion operator). By constructing your classes properly, you can take advantage of this rule so that the object constructions you want to allow are legal, but the implicit conversions you don't want to allow are
Consider the Array
template again. You need a way to allow an integer specifying the size of the array to be used as a constructor argument, but you must at the same time prevent the implicit conversion of an integer into a temporary Array
object. You accomplish this by first creating a new class, ArraySize
. Objects of this type have only one purpose: they represent the size of an array that's about to be created. You then modify Array
's single-argument constructor to take an ArraySize
object instead of an int
. The code looks like
template<class T> class Array { public: class ArraySize { // this class is new public: ArraySize(int numElements): theSize(numElements) {} int size() const { return theSize; } private: int theSize; }; Array(int lowBound, int highBound); Array(ArraySize size); // note new declaration ... };
Here you've nested ArraySize
inside Array
to emphasize the fact that it's always used in conjunction with that class. You've also made ArraySize
public in Array
so that anybody can use it.
Consider what happens when an Array
object is defined via the class's single-argument
Array<int> a(10);
Your compilers are asked to call a constructor in the Array<int>
class that takes an int
, but there is no such constructor. Compilers realize they can convert the int
argument into a temporary ArraySize
object, and that ArraySize
object is just what the Array<int>
constructor needs, so compilers perform the conversion with their usual gusto. This allows the function call (and the attendant object construction) to
The fact that you can still construct Array
objects with an int
argument is reassuring, but it does you little good unless the type conversions you want to avoid are prevented. They are. Consider this code
bool operator==(const Array<int>& lhs, const Array<int>& rhs);
Array<int> a(10); Array<int> b(10);
...
for (int i = 0; i < 10; ++i) if (a == b[i]) ... // oops! "a" should be "a[i]"; // this is now an error
Compilers need an object of type Array<int>
on the right-hand side of the "==
" in order to call operator==
for Array<int>
objects, but there is no single-argument constructor taking an int
argument. Furthermore, compilers cannot consider converting the int
into a temporary ArraySize
object and then creating the necessary Array<int>
object from this temporary, because that would call for two user-defined conversions, one from int
to ArraySize
and one from ArraySize
to Array<int>
. Such a conversion sequence is verboten, so compilers must issue an error for the code attempting to perform the
The use of the ArraySize
class in this example might look like a special-purpose hack, but it's actually a specific instance of a more general technique. Classes like ArraySize
are often called proxy classes, because each object of such a class stands for (is a proxy for) some other object. An ArraySize
object is really just a stand-in for the integer used to specify the size of the Array
being created. Proxy objects can give you control over aspects of your software's behavior in this case implicit type conversions that is otherwise beyond your grasp, so it's well worth your while to learn how to use them. How, you might wonder, can you acquire such learning? One way is to turn to Item 30; it's devoted to proxy
Before you turn to proxy classes, however, reflect a bit on the lessons of this Item. Granting compilers license to perform implicit type conversions usually leads to more harm than good, so don't provide conversion functions unless you're sure you want