What is the effect of 'explicit' keyword o

2019-07-11 19:05发布

问题:

Following code works perfectly fine (showing RVO):

struct A { 
  A (int) { cout << "A::A()\n"; }  // constructor
  A (const A&) { cout << "A::A(const A&)\n"; }  // copy constructor
};

A foo () { return A(0); }

int main () {
  A a = foo();
}

Output:

A::A()  // --> which means copy constructor is not called

If I mark the copy constructor as explicit:

explicit A (const A&) { ... }

Then the compiler errors out:

explicit.cpp: In function ‘A foo()’:
explicit.cpp:10:22: error: no matching function for call to ‘A::A(A)’
 A foo () { return A(0); }
                      ^
explicit.cpp:5:3: note: candidate: A::A(int)
   A (int) { cout << "A::A()\n"; }
   ^
explicit.cpp:5:3: note:   no known conversion for argument 1 from ‘A’ to ‘int’
explicit.cpp: In function ‘int main()’:
explicit.cpp:14:13: error: no matching function for call to ‘A::A(A)’
   A a = foo();
             ^
explicit.cpp:5:3: note: candidate: A::A(int)
   A (int) { cout << "A::A()\n"; }
   ^
explicit.cpp:5:3: note:   no known conversion for argument 1 from ‘A’ to ‘int’

Why is it happening, Shouldn't the RVO work as it is?

回答1:

RVO can elide a copy, but the language rules require that a copy (or a move) must still be possible:

[C++14: 12.8/31]: When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects. [..]

[C++14: 12.8/32]: [..] [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. —end note ]

You made the copy impossible by adding explicit, and a move is not possible because your copy constructor blocks the creation of an implicitly-defined move constructor.

You could allow a move instead by adding your own move constructor, perhaps a defaulted one:

A(A&&) = default;

But this is only another way of abiding by the same language rule.

C++17 will relax the rule somewhat anyway, by adding some guarantees of copy elision which will not be subject to this constraint.