27

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?

skydoor
  • 25,218
  • 52
  • 147
  • 201

8 Answers8

15

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.

Brian R. Bondy
  • 339,232
  • 124
  • 596
  • 636
  • explicit is also applicable when you got constructor with many parameters and all of them except for first have default values, for example: explicit RealWorldObject(int a, int b = 15, char c = 'a'); – chester89 Feb 27 '10 at 15:06
  • explicit can also be added to *stop* a compile error. Think of a smart pointer to `T`, which has a conversion `operator T *()` and also a copy constructor from `T *`. When you have expressions like `(a == 0)`, does that mean call the cast operator on `a` and use `==` on the two raw pointers, or should it construct a smart pointer from `0` and use `==` on the two smart pointers? Making the constructor explicit resolves this ambiguity. Though you're better off without the conversion operator in the first place for a variety of reasons. e.g. `shared_ptr` has explicit copy and no conversion. – Daniel Earwicker Feb 28 '10 at 10:16
9

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 ));
Nikolai Fetissov
  • 82,306
  • 11
  • 110
  • 171
  • b=1 is equal to b(1) here, but why it is harmful? – skydoor Feb 27 '10 at 03:20
  • 1
    Because simple syntax hides potentially expensive operations - construction of temporary, copy, destruction of temporary. – Nikolai Fetissov Feb 27 '10 at 03:27
  • 2
    Because it is unexpected. The syntax looks like you are setting b equal to 1, but instead it creates a new object. It hides what is really going on. http://blogs.msdn.com/oldnewthing/archive/2006/05/24/605974.aspx – shf301 Feb 27 '10 at 03:29
  • 5
    @Nikolai: actually, that's not a problem. Everything in C++ hides expensive operations, for example the overloaded `operator+`. The real reason to use it is when it does something that the person writing it may not expect; by using the full syntax hopefully they will either look or the IDE will look for them – Andreas Bonini Feb 27 '10 at 03:30
  • Wasn't that what I said originally? – Nikolai Fetissov Feb 27 '10 at 03:36
  • No object copying for me, I put a breakpoint in the copy constructor and inside the constructor that uses the implicit construction, and only 1 object is ever created. This is with a Debug config by the way, not a release config that is optimized. Optimizations are disabled. – Brian R. Bondy Feb 27 '10 at 03:38
  • The reason there is copying in your answer has nothing to do with `explicit`. You can also reproduce the same behavior by doing `Bar b; b = Bar();`. If you do `Bar b = 1;` you will not have an extra temporary object. – Brian R. Bondy Feb 27 '10 at 03:48
  • Ok, how about function parameters then - func( const Bar& ) called as func( 1 );? BTW, how do you highlight code into comments? – Nikolai Fetissov Feb 27 '10 at 04:29
  • @Nikolai N Fetissov: You can highlight code by using enclosing backquotes. I'm not sure what you mean exactly. – Brian R. Bondy Feb 27 '10 at 04:48
  • @Brian, it's not copy constructor, but copy assignment operator that's involved here. – Nikolai Fetissov Feb 27 '10 at 05:04
  • @Nikolai N Fetissov: Tried that as well, only the constructor is called once and no copy constructor or assignment operator. `Bar b = 1;` – Brian R. Bondy Feb 27 '10 at 12:56
  • Ok, let me re-phrase the answer. Let's see if we can agree there. – Nikolai Fetissov Feb 27 '10 at 16:22
6

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.

Andreas Bonini
  • 44,018
  • 30
  • 122
  • 156
4

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.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
2

It's not harmful for the experienced. May be harmful for beginner or a fresher debugging other's code.

Rajendra Uppal
  • 19,218
  • 15
  • 59
  • 57
  • 2
    But on the other hand, unless the implicit conversion is intuitive, does it hurt having to type a few more characters so that future maintainers (or even yourself) see exactly what's happening? – Mark B Feb 27 '10 at 18:21
1

"Harmful" is a strong statement. "Not something to be used without thought" is a good one. Much of C++ is that way (though some could argue some parts of C++ are harmful...)

Anyway, the worst part of implicit conversion is that not only can it happen when you don't expect it, but unless I'm mistaken, it can chain... as long as an implicit conversion path exists between type Foo and type Bar, the compiler will find it, and convert along that path - which may have many side effects that you didn't expect.

If the only thing it gains you is not having to type a few characters, it's just not worth it. Being explicit means you know what is actually happening and won't get bit.

kyoryu
  • 12,848
  • 2
  • 29
  • 33
0

To expand Brian's answer, consider you have this:

class MyString
{
public:
    MyString(int size)
        : size(size)
    {
    }

    // ...
};

This actually allows this code to compile:

MyString mystr;
// ...
if (mystr == 5)
    // ... do something

The compiler doesn't have an operator== to compare MyString to an int, but it knows how to make a MyString out of an int, so it looks at the if statement like this:

if (mystr == MyString(5))

That's very misleading since it looks like it's comparing the string to a number. In fact this type of comparison is probably never useful, assuming the MyString(int) constructor creates an empty string. If you mark the constructor as explicit, this type of conversion is disabled. So be careful with implicit conversions - be aware of all the types of statements that it will allow.

AshleysBrain
  • 22,335
  • 15
  • 88
  • 124
0

I use explicit as my default choice for converting (single parameter or equivalent) constructors. I'd rather have the compiler tell me immediately when I'm converting between one class and another and make the decision at that point if the conversion is appropriate or instead change my design or implementation to remove the need for the conversion completely.

Harmful is a slightly strong word for implicit conversions. It's harmful not so much for the initial implementation, but for maintenance of applications. Implicit conversions allow the compiler to silently change types, especially in parameters to yet another function call - for example automatically converting an int into some other object type. If you accidentally pass an int into that parameter the compiler will "helpfully" silently create the temporary for you, leaving you perplexed when things don't work right. Sure we can all say "oh, I'll never make that mistake", but it only takes one time debugging for hours before one starts thinking maybe having the compiler tell you about those conversions is a good idea.

Mark B
  • 95,107
  • 10
  • 109
  • 188