Actualización de C ++ 17
En C ++ 17, el significado de A_factory_func()
cambiar de crear un objeto temporal (C ++ <= 14) a solo especificar la inicialización de cualquier objeto al que se inicialice esta expresión (en términos generales) en C ++ 17. Estos objetos (llamados "objetos de resultado") son las variables creadas por una declaración (como a1
), objetos artificiales creados cuando la inicialización termina siendo descartada, o si se necesita un objeto para el enlace de referencia (como, en A_factory_func();
. En el último caso, un objeto se crea artificialmente, llamado "materialización temporal", porque A_factory_func()
no tiene una variable o referencia que de lo contrario requeriría que exista un objeto).
Como ejemplos en nuestro caso, en el caso de a1
y a2
las reglas especiales dicen que en tales declaraciones, el objeto resultante de un inicializador prvalue del mismo tipo que a1
es variable a1
, y por lo tanto, A_factory_func()
inicializa directamente el objeto a1
. Cualquier molde intermedio de estilo funcional no tendría ningún efecto, ya que A_factory_func(another-prvalue)
simplemente "pasa a través" del objeto de resultado del prvalue externo para ser también el objeto de resultado del prvalue interno.
A a1 = A_factory_func();
A a2(A_factory_func());
Depende de qué tipo A_factory_func()
devuelve. Supongo que devuelve un A
, luego está haciendo lo mismo, excepto que cuando el constructor de copia es explícito, entonces el primero fallará. Leer 8.6 / 14
double b1 = 0.5;
double b2(0.5);
Esto está haciendo lo mismo porque es un tipo incorporado (esto significa que no es un tipo de clase aquí). Leer 8,6 / 14 .
A c1;
A c2 = A();
A c3(A());
Esto no está haciendo lo mismo. El primer valor predeterminado se inicializa si no A
es un POD y no se inicializa para un POD (Leer 8.6 / 9 ). La segunda copia se inicializa: el valor inicializa un valor temporal y luego copia ese valor en c2
(Leer 5.2.3 / 2 y 8.6 / 14 ). Por supuesto, esto requerirá un constructor de copia no explícito (Lea 8.6 / 14 y 12.3.1 / 3 y 13.3.1.3/1 ). El tercero crea una declaración de función para una función c3
que devuelve un A
y que toma un puntero de función a una función que devuelve un A
(Leer 8.2 ).
Profundizando en Inicializaciones Directa y Copia de inicialización
Si bien se ven idénticos y se supone que deben hacer lo mismo, estas dos formas son notablemente diferentes en ciertos casos. Las dos formas de inicialización son directa e inicialización de copia:
T t(x);
T t = x;
Hay un comportamiento que podemos atribuir a cada uno de ellos:
- La inicialización directa se comporta como una llamada a una función sobrecargada: las funciones, en este caso, son las constructoras
T
(incluidas explicit
las), y el argumento es x
. La resolución de sobrecarga encontrará el mejor constructor coincidente y, cuando sea necesario, realizará cualquier conversión implícita requerida.
- La inicialización de copia construye una secuencia de conversión implícita: intenta convertir
x
a un objeto de tipo T
. (Luego puede copiar sobre ese objeto en el objeto inicializado, por lo que también se necesita un constructor de copia, pero esto no es importante a continuación)
Como puede ver, la inicialización de copia es de alguna manera parte de la inicialización directa con respecto a posibles conversiones implícitas: mientras que la inicialización directa tiene todos los constructores disponibles para llamar, y además puede hacer cualquier conversión implícita que necesite para emparejar tipos de argumentos, inicialización de copia solo puede configurar una secuencia de conversión implícita.
Lo intenté mucho y obtuve el siguiente código para generar texto diferente para cada una de esas formas , sin usar el "obvio" a través de los explicit
constructores.
#include <iostream>
struct B;
struct A {
operator B();
};
struct B {
B() { }
B(A const&) { std::cout << "<direct> "; }
};
A::operator B() { std::cout << "<copy> "; return B(); }
int main() {
A a;
B b1(a); // 1)
B b2 = a; // 2)
}
// output: <direct> <copy>
¿Cómo funciona y por qué genera ese resultado?
Inicialización directa
Primero no sabe nada acerca de la conversión. Solo intentará llamar a un constructor. En este caso, el siguiente constructor está disponible y es una coincidencia exacta :
B(A const&)
No hay conversión, y mucho menos una conversión definida por el usuario, necesaria para llamar a ese constructor (tenga en cuenta que aquí tampoco ocurre ninguna conversión de calificación constante). Y así, la inicialización directa lo llamará.
Copia de inicialización
Como se dijo anteriormente, la inicialización de copia construirá una secuencia de conversión cuando a
no se ha escrito B
o derivado de ella (lo cual es claramente el caso aquí). Por lo tanto, buscará formas de realizar la conversión y encontrará los siguientes candidatos
B(A const&)
operator B(A&);
Observe cómo reescribí la función de conversión: el tipo de parámetro refleja el tipo del this
puntero, que en una función miembro no constante es no constante. Ahora, llamamos a estos candidatos x
como argumento. El ganador es la función de conversión: porque si tenemos dos funciones candidatas que aceptan una referencia al mismo tipo, entonces la versión menos constante gana (esto es, por cierto, también el mecanismo que prefiere llamadas de funciones miembro no constantes para -const objetos).
Tenga en cuenta que si cambiamos la función de conversión para que sea una función miembro constante, entonces la conversión es ambigua (porque ambos tienen un tipo de parámetro de A const&
entonces): el compilador Comeau la rechaza correctamente, pero GCC la acepta en modo no pedante. Sin -pedantic
embargo, cambiar a hace que también genere la advertencia de ambigüedad adecuada.
¡Espero que esto ayude un poco para aclarar cómo difieren estas dos formas!
A c1; A c2 = c1; A c3(c1);
.