Search code examples
c++explicitcopy-initialization

What actually happens when copy initializing in C++?


Consider the following code:

#include<iostream>
using namespace std;
class A{
public:
  A()=default;
  A(int a){cout<<"A(int) called"<<endl;}
  A(const A&a);
  ~A(){cout<<"~A() called"<<endl;}
};
A::A(const A&a){
  cout<<"A(const A&) called"<<endl;
}
int main(){
   A a = 1;
}

When I use g++8.1 to compile with -fno-elide-constructors to cancel the RVO, the output was:

A(int) called
A(const A&) called
~A() called
~A() called

I know that this is something called the converting constructor, an implicit conversion from the types of its arguments to the type of its class.

It seems that firstly a temporary object is constructed by A(int), secondly the a object is constructed by the copy-constructor A(const A&).

But when I modify my code:

#include<iostream>
using namespace std;
class A{
public:
  A()=default;
  A(int a){cout<<"A(int) called"<<endl;}
  explicit A(const A&a); //use explicit
  ~A(){cout<<"~A() called"<<endl;}
};
A::A(const A&a){
  cout<<"A(const A&) called"<<endl;
}
int main(){
   //A a = A(1); //error:no constructor match
   A b = 1; //ok
}

It confused me that the b object is copy-constructed explicitly?! Even if I use the copy initialization?
When I delete the copy-constructor by A(const A&a)=delete;, it will not work as expected.

However, it is different when I use VS2017. the implicit conversion A a = 1; has nothing to do with the copy-constructor. Even if I delete the c-c, it works as usual.


Solution

  • The effect of copy initialization here is,

    (emphasis mine)

    If T is a class type, and the cv-unqualified version of the type of other is not T or derived from T, or if T is non-class type, but the type of other is a class type, user-defined conversion sequences that can convert from the type of other to T (or to a type derived from T if T is a class type and a conversion function is available) are examined and the best one is selected through overload resolution. The result of the conversion, which is a prvalue temporary (until C++17) prvalue expression (since C++17) if a converting constructor was used, is then used to direct-initialize the object. The last step is usually optimized out and the result of the conversion is constructed directly in the memory allocated for the target object, but the appropriate constructor (move or copy) is required to be accessible even though it's not used. (until C++17)

    Note that the ojbect is direct-initialized from the converted A (from int), the copy constructor is marked as explicit or not doesn't matter here.


    BTW: Since C++17 because of mandatory copy elision, the copy-construction will be elided completely.

    Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible: