why implicit conversion is harmful in C++
I understand that the keyword explicit can be used to prevent implicit conversion.
For example
Foo {
public:
explicit Foo(int i) {}
}
My question is, under what condition, implicit conversion should be prohibited? Why implicit conversion is harmful?
Use explicit
when you would prefer a compiling error.
explicit
is only applicable when there is one parameter in your constructor (or many where the first is the only one without a default value).
You would want to use the explicit
keyword anytime that the programmer may construct an object by mistake, thinking it may do something it is not actually doing.
Here's an example:
class MyString
{
public:
MyString(int size)
: size(size)
{
}
//... other stuff
int size;
};
With the following code you are allowed to do this:
int age = 29;
//...
//Lots of code
//...
//Pretend at this point the programmer forgot the type of x and thought string
str s = x;
But the caller probably meant to store "3" inside the MyString variable and not 3. It is better to get a compiling error so the user can call itoa or some other conversion function on the x variable first.
The new code that will produce a compiling error for the above code:
class MyString
{
public:
explicit MyString(int size)
: size(size)
{
}
//... other stuff
int size;
};
Compiling errors are always better than bugs because they are immediately visible for you to correct.
It introduces unexpected temporaries:
struct Bar
{
Bar(); // default constructor
Bar( int ); // value constructor with implicit conversion
};
void func( const Bar& );
Bar b;
b = 1; // expands to b.operator=( Bar( 1 ));
func( 10 ); // expands to func( Bar( 10 ));
A real world example:
class VersionNumber
{
public:
VersionNumber(int major, int minor, int patch = 0, char letter = '\0') : mMajor(major), mMinor(minor), mPatch(patch), mLetter(letter) {}
explicit VersionNumber(uint32 encoded_version) { memcpy(&mLetter, &encoded_version, 4); }
uint32 Encode() const { int ret; memcpy(&ret, &mLetter, 4); return ret; }
protected:
char mLetter;
uint8 mPatch;
uint8 mMinor;
uint8 mMajor;
};
VersionNumber v = 10;
would almost certainly be an error, so the explicit
keyword requires the programmer to type VersionNumber v(10);
and - if he or she is using a decent IDE - they will notice through the IntelliSense popup that it wants an encoded_version
.
Mostly implicit conversion is a problem when it allows code to compile (and probably do something strange) in a situation where you did something you didn't intend, and would rather the code didn't compile, but instead some conversion allows the code to compile and do something strange.
For example, iostreams have a conversion to void *
. If you're bit tired and type in something like: std::cout << std::cout;
it will actually compile -- and produce some worthless result -- typically something like an 8 or 16 digit hexadecimal number (8 digits on a 32-bit system, 16 digits on a 64-bit system).
At the same time, I feel obliged to point out that a lot of people seem to have gotten an almost reflexive aversion to implicit conversions of any kind. There are classes for which implicit conversions make sense. A proxy class, for example, allows conversion to one other specific type. Conversion to that type is never unexpected for a proxy, because it's just a proxy -- i.e. it's something you can (and should) think of as completely equivalent to the type for which it's a proxy -- except of course that to do any good, it has to implement some special behavior for some sort of specific situation.
For example, years ago I wrote a bounded<T>
class that represents an (integer) type that always remains within a specified range. Other that refusing to be assigned a value outside the specified range, it acts exactly like the underlying intger type. It does that (largely) by providing an implicit conversion to int. Just about anything you do with it, it'll act like an int. Essentially the only exception is when you assign a value to it -- then it'll throw an exception if the value is out of range.
It's not harmful for the experienced. May be harmful for beginner or a fresher debugging other's code.