c++ initialization history language-design

c++ - ¿Cuál es la motivación detrás de tener copia y la inicialización directa se comportan de manera diferente?



initialization history (4)

Algo relacionado con ¿Por qué se llama copiar al constructor en lugar de al constructor de conversión?

Hay dos sintaxis para la inicialización, la inicialización directa y la copia:

A a(b); A a = b;

Quiero conocer la motivación para que tengan diferentes conductas definidas. Para la inicialización de la copia, hay una copia adicional, y no se me ocurre ningún propósito para esa copia. Como se trata de una copia de una plantilla, puede y probablemente se optimizará, por lo que el usuario no puede confiar en que suceda, por lo que la copia adicional en sí no es motivo suficiente para el comportamiento diferente. ¿Entonces por qué?


Ya que es una copia de una temp, puede y probablemente será optimizada

La palabra clave aquí es probablemente . El estándar permite, pero no requiere, un compilador para optimizar la copia. Si algunos compiladores permitieron este código (optimizado), pero otros lo rechazaron (no optimizado), esto sería muy inconsistente.

Así que el estándar prescribe una forma consistente de manejar esto: todos deben verificar que el constructor de copias sea accesible, ya sea que lo usen o no.

La idea es que todos los compiladores deben aceptar el código o rechazarlo. De lo contrario no será portátil.

Otro ejemplo, considere

A a; B b; A a1 = a; A a2 = b;

Sería igualmente inconsistente permitir a2 pero prohibir a1 cuando el constructor de copias de A es privado.

También podemos ver en el texto Estándar que los dos métodos para inicializar un objeto de clase fueron diferentes (8.5 / 16):

Si la inicialización es una inicialización directa, o si es una inicialización de copia en la que la versión no calificada cv del tipo de origen es la misma clase que la clase de destino o una clase derivada de la clase de destino, se consideran constructores. Los constructores correspondientes se enumeran (13.3.1.3) y el mejor se elige mediante resolución de sobrecarga (13.3). El constructor así seleccionado se llama para inicializar el objeto, con la expresión inicializadora o lista de expresiones como su argumento (s). Si no se aplica ningún constructor, o la resolución de sobrecarga es ambigua, la inicialización está mal formada.

De lo contrario (es decir, para los casos restantes de inicialización de copia), las secuencias de conversión definidas por el usuario que pueden convertirse del tipo de origen al tipo de destino o (cuando se usa una función de conversión) a una clase derivada del mismo se enumeran como se describe en 13.3. 1.4, y la mejor se elige mediante resolución de sobrecarga (13.3). Si la conversión no se puede realizar o es ambigua, la inicialización no se ha realizado correctamente. La función seleccionada se llama con la expresión inicializadora como su argumento; si la función es un constructor, la llamada inicializa un temporal de la versión no calificada cv del tipo de destino. El temporal es un prvalue. El resultado de la llamada (que es el temporal para el caso del constructor) se utiliza para inicializar directamente, de acuerdo con las reglas anteriores, el objeto que es el destino de la inicialización de la copia. En ciertos casos, se permite una implementación para eliminar la copia inherente a esta inicialización directa mediante la construcción del resultado intermedio directamente en el objeto que se está inicializando; ver 12.2, 12.8.

Una diferencia es que la inicialización directa utiliza directamente los constructores de la clase construida. Con la inicialización de la copia, se consideran otras funciones de conversión y estas pueden producir un temporal que debe copiarse.


Inicialización de tipos incorporados como:

int i = 2;

Es una sintaxis muy natural, en parte debido a razones históricas (recuerda las matemáticas de la escuela secundaria). Es más natural que:

int i(2);

Incluso si algunos matemáticos pueden discutir este punto. Después de todo, no hay nada antinatural en llamar a una función (un constructor en este caso) y pasarle un argumento.

Para los tipos incorporados, estos dos tipos de inicialización son idénticos. No hay copia extra en el caso anterior. Esa es la razón para tener ambos tipos de inicialización y originalmente no había una intención específica para hacer que se comportaran de manera diferente.

Sin embargo, hay tipos definidos por el usuario y uno de los objetivos establecidos del lenguaje es permitirles comportarse como tipos integrados lo más cerca posible.

Por lo tanto, la construcción de copia (tomando entrada de alguna función de conversión, por ejemplo) es la implementación natural de la primera sintaxis.

El hecho de que pueda tener copias adicionales y que puedan ser eliminadas es una optimización para los tipos definidos por el usuario. Tanto elección de la copia como los constructores explícitos llegaron mucho más tarde al lenguaje. No es sorprendente que el estándar permita optimizaciones después de un cierto período de uso. Además, ahora puede eliminar constructores explícitos de los candidatos de resolución de sobrecarga.


Solo una especulación, pero me temo que será difícil estar más seguro sin que Bjarne Stroustrup confirme cómo fue realmente:

Fue diseñado de esta manera porque se suponía que el programador esperaría tal comportamiento, que esperará que la copia se realice cuando se use el signo = y no con la sintaxis del inicializador directo.

Creo que la posible elision de copia solo se agregó en versiones posteriores del estándar, pero no estoy seguro, esto es algo que alguien puede decir sin duda al verificar el historial estándar.


Tomemos el siguiente ejemplo:

struct X { X(int); X(const X&); }; int foo(X x){/*Do stuff*/ return 1; } X x(1); foo(x);

En los compiladores que probé, el argumento de foo siempre se copió incluso con la optimización completa activada. De esto, podemos reunir que las copias no serán eliminadas en todas las situaciones.

Ahora pensemos desde una perspectiva de diseño de lenguaje, imagine todos los escenarios en los que tendría que pensar si quisiera establecer reglas para cuándo se necesita una copia y cuándo no. Esto sería muy difícil. Además, incluso si pudieras crear reglas, serían muy complejas y casi imposibles de comprender para las personas. Sin embargo, al mismo tiempo, si forzaste copias en todas partes, sería muy ineficiente. Esta es la razón por la cual las reglas son como son, usted hace que las reglas sean comprensibles para que las personas las entiendan sin dejar de forzar las copias si se pueden evitar.

Tengo que admitir que ahora, esta respuesta es muy similar a la de Suma. La idea es que puede esperar el comportamiento con las reglas actuales, y cualquier otra cosa sería demasiado difícil de seguir para las personas.