Supongamos que tengo esta función:
void my_test()
{
A a1 = A_factory_func();
A a2(A_factory_func());
double b1 = 0.5;
double b2(0.5);
A c1;
A c2 = A();
A c3(A());
}
En cada grupo, ¿son idénticas estas afirmaciones? ¿O hay una copia adicional (posiblemente optimizable) en algunas de las inicializaciones?
He visto a gente decir las dos cosas. Por favor, cite el texto como prueba. También agregue otros casos por favor.
c++
initialization
rlbond
fuente
fuente
A c1; A c2 = c1; A c3(c1);
.A
, la inicialización de la copia no requeriría la existencia de un constructor de copia / movimiento. Por esostd::atomic<int> a = 1;
está bien en C ++ 17 pero no antes.Respuestas:
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 (comoa1
), objetos artificiales creados cuando la inicialización termina siendo descartada, o si se necesita un objeto para el enlace de referencia (como, enA_factory_func();
. En el último caso, un objeto se crea artificialmente, llamado "materialización temporal", porqueA_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
ya2
las reglas especiales dicen que en tales declaraciones, el objeto resultante de un inicializador prvalue del mismo tipo quea1
es variablea1
, y por lo tanto,A_factory_func()
inicializa directamente el objetoa1
. Cualquier molde intermedio de estilo funcional no tendría ningún efecto, ya queA_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.Depende de qué tipo
A_factory_func()
devuelve. Supongo que devuelve unA
, luego está haciendo lo mismo, excepto que cuando el constructor de copia es explícito, entonces el primero fallará. Leer 8.6 / 14Esto está haciendo lo mismo porque es un tipo incorporado (esto significa que no es un tipo de clase aquí). Leer 8,6 / 14 .
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 enc2
(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ónc3
que devuelve unA
y que toma un puntero de función a una función que devuelve unA
(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:
Hay un comportamiento que podemos atribuir a cada uno de ellos:
T
(incluidasexplicit
las), y el argumento esx
. La resolución de sobrecarga encontrará el mejor constructor coincidente y, cuando sea necesario, realizará cualquier conversión implícita requerida.x
a un objeto de tipoT
. (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.¿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 :
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 escritoB
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 candidatosObserve 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 candidatosx
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!
fuente
R() == R(*)()
yT[] == T*
. Es decir, los tipos de función son tipos de puntero de función y los tipos de matriz son tipos de puntero a elemento. Esto apesta. Puede serA c3((A()));
solucionado por (parens alrededor de la expresión).La asignación es diferente de la inicialización .
Ambas líneas hacen la inicialización . Se realiza una sola llamada de constructor:
pero no es equivalente a:
No tengo un texto en este momento para probar esto, pero es muy fácil experimentar:
fuente
double b1 = 0.5;
Es una llamada implícita del constructor.double b2(0.5);
Es una llamada explícita.Mira el siguiente código para ver la diferencia:
Si su clase no tiene constructores explícitos, las llamadas explícitas e implícitas son idénticas.
fuente
Primera agrupación: depende de lo que
A_factory_func
regrese. La primera línea es un ejemplo de inicialización de copia , la segunda línea es inicialización directa . SiA_factory_func
devuelve unA
objeto, entonces son equivalentes, ambos llaman al constructor de la copiaA
, de lo contrario, la primera versión crea un valor de tipo aA
partir de operadores de conversión disponibles para el tipo de retornoA_factory_func
o losA
constructores apropiados , y luego llama al constructor de la copia para construir aa1
partir de este temporal. La segunda versión intenta encontrar un constructor adecuado que tome lo que sea queA_factory_func
devuelva, o que tome algo a lo que el valor de retorno se pueda convertir implícitamente.Segunda agrupación: exactamente la misma lógica, excepto que los tipos incorporados no tienen constructores exóticos, por lo que son, en la práctica, idénticos.
Tercera agrupación:
c1
se inicializa por defecto,c2
se inicializa por copia desde un valor inicializado temporalmente.c1
No se puede inicializar ningún miembro que tenga un tipo de pod (o miembros de miembros, etc., etc.) si el usuario proporcionó los constructores predeterminados (si los hay) no los inicializa explícitamente. Parac2
, depende de si hay un constructor de copias proporcionado por el usuario y si eso inicializa adecuadamente a esos miembros, pero los miembros de la temporal se inicializarán todos (inicializados a cero si no se inicializan explícitamente). Como Litb vio,c3
es una trampa. En realidad es una declaración de función.fuente
De nota:
[12,2 / 1]
Temporaries of class type are created in various contexts: ... and in some initializations (8.5).
Es decir, para inicialización de copia.
[12,8 / 15]
When certain criteria are met, an implementation is allowed to omit the copy construction of a class object ...
En otras palabras, un buen compilador no creará una copia para la inicialización de la copia cuando se pueda evitar; en su lugar, solo llamará al constructor directamente, es decir, al igual que para la inicialización directa.
En otras palabras, la inicialización de copia es como la inicialización directa en la mayoría de los casos <opinion> donde se ha escrito un código comprensible. Dado que la inicialización directa puede causar conversiones arbitrarias (y, por lo tanto, probablemente desconocidas), prefiero usar siempre la inicialización de copia cuando sea posible. (Con la ventaja de que en realidad parece una inicialización). </opinion>
Goriness técnico: [12.2 / 1 cont desde arriba]
Even when the creation of the temporary object is avoided (12.8), all the semantic restrictions must be respected as if the temporary object was created.
Me alegro de no estar escribiendo un compilador de C ++.
fuente
Puede ver su diferencia
explicit
yimplicit
los tipos de constructor cuando inicializa un objeto:Clases
Y en la
main
función:Por defecto, un constructor es
implicit
así que tiene dos formas de inicializarlo:Y al definir una estructura como
explicit
solo tienes una forma directa:fuente
Respondiendo con respecto a esta parte:
Como la mayoría de las respuestas son pre-c ++ 11, estoy agregando lo que c ++ 11 tiene que decir sobre esto:
Entonces, la optimización o no son equivalentes según el estándar. Tenga en cuenta que esto está de acuerdo con lo que otras respuestas han mencionado. Simplemente citando lo que el estándar tiene que decir en aras de la corrección.
fuente
Muchos de estos casos están sujetos a la implementación de un objeto, por lo que es difícil darle una respuesta concreta.
Considera el caso
En este caso, suponiendo un operador de asignación adecuado y un constructor de inicialización que acepte un único argumento entero, la forma en que implemente dichos métodos afecta el comportamiento de cada línea. Sin embargo, es una práctica común que uno de ellos llame al otro en la implementación para eliminar el código duplicado (aunque en un caso tan simple como este no habría un propósito real).
Editar: como se mencionó en otras respuestas, la primera línea llamará al constructor de la copia. Considere los comentarios relacionados con el operador de asignación como comportamiento relacionado con una asignación independiente.
Dicho esto, cómo el compilador optimiza el código tendrá su propio impacto. Si tengo el constructor de inicialización que llama al operador "=", si el compilador no realiza optimizaciones, la línea superior realizará 2 saltos en lugar de uno en la línea inferior.
Ahora, para las situaciones más comunes, su compilador optimizará estos casos y eliminará este tipo de ineficiencias. Entonces, efectivamente, todas las diferentes situaciones que describas serán las mismas. Si desea ver exactamente lo que se está haciendo, puede mirar el código objeto o la salida de un ensamblador de su compilador.
fuente
operator =(const int)
y noA(const int)
. Vea la respuesta de @ jia3ep para más detalles.Esto es del lenguaje de programación C ++ de Bjarne Stroustrup:
Una inicialización con an = se considera una inicialización de copia . En principio, una copia del inicializador (el objeto del que estamos copiando) se coloca en el objeto inicializado. Sin embargo, dicha copia puede optimizarse (eliminarse) y puede usarse una operación de movimiento (basada en la semántica de movimiento) si el inicializador es un valor r. Dejar fuera = hace explícita la inicialización. La inicialización explícita se conoce como inicialización directa .
fuente