Considere el siguiente código:

#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;
}

Cuando uso g ++ 8.1 para compilar con -fno-elide-constructors para cancelar el RVO, el resultado fue:

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

Sé que esto es algo llamado el constructor de conversión, una conversión implícita de los tipos de sus argumentos al tipo de su clase.

Parece que, en primer lugar, A(int) construye un objeto temporal; en segundo lugar, el constructor de copia A(const A&) construye el objeto a.

Pero cuando modifico mi código:

#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
}

¡Me confundió que el objeto b está construido con copia explícitamente ?! ¿Incluso si uso la inicialización de copia?
Cuando elimino el constructor de copias por A(const A&a)=delete;, no funcionará como se esperaba.

Sin embargo, es diferente cuando uso VS2017. la conversión implícita A a = 1; no tiene nada que ver con el constructor de copias. Incluso si elimino el c-c, funciona como de costumbre.

3
sakugawa 21 jul. 2020 a las 10:12

2 respuestas

La mejor respuesta

El efecto de inicialización de copia aquí es,

(énfasis mío)

Si T es un tipo de clase, y la versión no calificada por cv del tipo de otro no es T o derivada de T, o si T es un tipo que no es de clase, pero el tipo de otro es un tipo de clase, secuencias de conversión definidas por el usuario que se pueden convertir del tipo de otro a T (o a un tipo derivado de T si T es un tipo de clase y hay una función de conversión disponible) se examinan y se selecciona el mejor mediante resolución de sobrecarga. El resultado de la conversión, que es un prvalue temporary (until C++17) prvalue expression (since C++17) si se utilizó un constructor de conversión, se usa para inicializar directamente el objeto . 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)

Tenga en cuenta que el objeto se inicializa directamente desde el A convertido (desde int), el constructor de la copia está marcado como explicit o no importa aquí.


Por cierto: desde C ++ 17 debido a copia obligatoria elisión, la copia- la construcción se elidirá por completo.

En las siguientes circunstancias, los compiladores deben omitir la construcción de copiar y mover objetos de clase, incluso si el constructor de copiar / mover y el destructor tienen efectos secundarios observables. Los objetos se construyen directamente en el almacenamiento donde, de lo contrario, se copiarían / moverían. Los constructores de copiar / mover no necesitan estar presentes o accesibles:

1
songyuanyao 21 jul. 2020 a las 09:55

La diferencia entre

A a = A(1);

Y

A b = 1;

Es que en el segundo caso, el efecto que aplica es el descrito en la respuesta de @ songyuanyao. Pero, el efecto en el primer caso es otro:

Si T es un tipo de clase y la versión no calificada por cv del tipo de otro es T o una clase derivada de T, los constructores no explícitos de T se examinan y la mejor coincidencia se selecciona por resolución de sobrecarga. Luego se llama al constructor para inicializar el objeto.

Es por eso que no funciona con el constructor de copia explicit.

1
Daniel Langr 21 jul. 2020 a las 07:26