0

Ive been looking into the explicit specifier and I was hoping I could have some feedback (small examples would be perfect) on the following:

  1. How does an explicit constructor prevent copy initialization?
  2. Can you provide a small example of how explicit has a great benefit over implicit constructors in terms of generating safer code?

Im not so concerned about explicit conversion functions (C++11), just the general ctor principles

Any feedback would be greatly appreciated

Thanks in advance

Spirit
  • 150
  • 1
  • 10
  • 1
    `explicit` does not prevent copy construction (or construction form a different type, aka conversion construction), but it prevents that from happening implicitly. – Emerald Weapon Oct 26 '16 at 08:50
  • Possible duplicate of [Disable copy constructor](http://stackoverflow.com/questions/6077143/disable-copy-constructor) – schultz Oct 26 '16 at 08:52

3 Answers3

1

An example for 1.:

struct Foo{};
struct Bar {
    explicit Bar(Foo){}    
};

int main() {
    Bar a {Foo{}}; // compiles
    Bar b = Foo{}; // error
}

a compiles because it uses direct initialization, so the explicit constructor can be used. b does not compile because it uses copy initialization.

Explicit constructors prevent you from accidentally doing implicit conversions:

void oops (const Bar&) {

}

int main() {
    oops (Foo{});
}

This constructs a temporary Bar from the Foo which is passed in and binds it to the reference parameter. This is pretty much hidden from the user, and might lead to surprising results if the construction is expensive or you expect the argument and parameter to be the same object. Explicit constructors protect you from this problem.

TartanLlama
  • 63,752
  • 13
  • 157
  • 193
  • Thank you for this, can you elaborate on the example using values to illustrate how a problem can arise? – Spirit Oct 26 '16 at 09:13
  • @Spirit I'm not quite sure what you're asking for. – TartanLlama Oct 26 '16 at 09:39
  • sorry, just wondering if its possible to show how implicit constrcutors can bring about unwanted values in the form of your example? – Spirit Oct 26 '16 at 09:40
  • That example shows an implicit conversion. A temporary `Bar` will be constructed from the `Foo` passed in, which is not obvious from the call site. If the constructor was explicit then you'd need to write `oops (Bar{Foo{}})`, which makes it clear that the conversion is what you want. – TartanLlama Oct 26 '16 at 09:54
1

From the cppreference page on copy-initialization:

Copy-initialization is less permissive than direct-initialization: explicit constructors are not converting constructors and are not considered for copy-initialization.

So the answer to your first question is "by design". Copy-initialization only considers implicit conversions.

As to why you'd want to disable implicit conversion: it is used as a safety measure when constructing the object might be dangerous, and should therefore be explicit (visible in the code).

For example, let's imagine that std::unique_ptr<T>'s constructor from T* be implicit. Now let's look at some (contrived) user code:

void forbulate(Foo * const myFoo) {
    myFoo->doStuff();
    lookAt(myFoo); // My Foo is amazing
    myFoo->doMoreStuff();
}

This looks fine at first sight. But what you don't know, is that lookAt actually has the following prototype:

void lookAt(std::unique_ptr<Foo> yourFoo);

So we are, in fact, silently constructing a unique_ptr<Foo> to pass to lookAt. this means something very important: this unique_ptr is now the owner of myFoo, and will kill it upon destruction. What actually happens is:

void forbulate(Foo * const myFoo) {

    myFoo->doStuff(); // Fine

    lookAt(myFoo)  // Still fine
                 ; // myFoo is `delete`d !

    myFoo->doMoreStuff(); // Ouch, undefined behaviour from accessing a dead object

} // Sorry caller, I accidentally your Foo :(

Note that the deletion itself might be UB already (for example if myFoo is the address of an automatic instance). In any case, this innocuous-looking code is actually a landmine. Not spreading landmines everywhere is why we have explicit constructors: with the actual unique_ptr specification, the code doesn't even compile, and you know what's wrong.

Quentin
  • 62,093
  • 7
  • 131
  • 191
1

My example:

class MyClass {
public:
    MyClass() = default;
    // copy constructor
    explicit MyClass(const MyClass& other) = default; 
    // conversion constructor
    explicit MyClass(int val) : val_(val) {}; // constructor that creates a MyClass object from an int.. in other words this 'converts' an int into a MyClass object

private:
    int val_;
};


void foo(MyClass n) {};

int main() {

    MyClass obj;

    MyClass obj2 = obj; // implicit use of the copy constructor - does not compile if the copy ctor is marked explicit
    MyClass obj3(obj); // explicit call to the copy ctr... always works

    foo(obj); // regular call to foo, copy ctor called
    foo(2); // implicit use of the conversion ctr - does not compile if the conversion ctor is marked explicit.
            // this automatic conversion could be risky/undesired in some cases.. therefore the need for the explicit keyword.
    return 0;
}
Emerald Weapon
  • 2,392
  • 18
  • 29