que - tipos de variables en c++ y sus rangos
¿Puede la inicialización de la expresión usar la propia variable? (3)
Creo que es válido (loco, pero válido).
Esto sería tanto legal como lógicamente aceptable:
Data d ;
d = fill( d ) ;
Y es que esta forma es la misma:
Data d = fill( d ) ;
En lo que respecta a la estructura lógica del lenguaje, esas dos versiones son equivalentes.
Así que es legal y lógicamente correcto para el lenguaje.
Sin embargo, como normalmente esperamos que las personas inicialicen las variables a un valor predeterminado cuando las creamos (por seguridad), es una mala práctica de programación .
Es interesante que g ++ -Wall compile este código sin un blurp.
Considere el siguiente código:
#include <iostream>
struct Data
{
int x, y;
};
Data fill(Data& data)
{
data.x=3;
data.y=6;
return data;
}
int main()
{
Data d=fill(d);
std::cout << "x=" << d.x << ", y=" << d.y << "/n";
}
Aquí d
se inicializa con copia desde el valor de retorno de fill()
, pero fill()
escribe en d
antes de devolver su resultado. Lo que me preocupa es que d
no se usa de forma no trivial antes de inicializarse, y el uso de variables no inicializadas en algunos (¿todos?) Casos conduce a un comportamiento indefinido.
Entonces, ¿este código es válido o tiene un comportamiento indefinido? Si es válido, ¿se volverá indefinido el comportamiento una vez que Data
deje de ser POD o en algún otro caso?
Esto no parece un código válido. Es similar al caso descrito en la pregunta: ¿es legal pasar un objeto C ++ a su propio constructor? , aunque en ese caso el código era válido. La mecánica no es idéntica, pero el razonamiento básico al menos puede ayudarnos a comenzar.
Comenzamos con el informe de defectos 363 que pregunta:
Y si es así, ¿cuál es la semántica de la autoinicialización de UDT? Por ejemplo
#include <stdio.h> struct A { A() { printf("A::A() %p/n", this); } A(const A& a) { printf("A::A(const A&) %p %p/n", this, &a); } ~A() { printf("A::~A() %p/n", this); } }; int main() { A a=a; }
Se pueden compilar e imprimir:
A::A(const A&) 0253FDD8 0253FDD8 A::~A() 0253FDD8
y la resolución propuesta era:
3.8 [basic.life] el párrafo 6 indica que las referencias aquí son válidas. Se permite tomar la dirección de un objeto de clase antes de que esté completamente inicializado, y se le permite pasarlo como un argumento a un parámetro de referencia siempre que la referencia se pueda vincular directamente. [...]
Entonces, aunque d
no está completamente inicializado, podemos pasarlo como referencia.
Donde empezamos a meternos en problemas es aquí:
data.x=3;
El borrador de la sección 3.8
C ++ estándar ( la misma sección y el párrafo que cita el informe de defectos ) dice ( énfasis mío ):
De manera similar, antes de que haya comenzado la vida útil de un objeto, pero después de que se haya asignado el almacenamiento que ocupará el objeto o, después de que haya finalizado la vida útil de un objeto y antes de que el almacenamiento que ocupa el objeto se reutilice o libere, cualquier valor de glualidad que se refiera El objeto original puede ser usado pero solo de manera limitada. Para un objeto en construcción o destrucción, ver 12.7. De lo contrario, dicho glvalue se refiere al almacenamiento asignado (3.7.4.2), y el uso de las propiedades del glvalue que no dependen de su valor está bien definido. El programa tiene un comportamiento indefinido si:
se aplica una conversión de lvalue a rvalue (4.1) a dicho glvalue,
el valor de glival se usa para acceder a un miembro de datos no estáticos o llamar a una función de miembro no estático del objeto, o
el glvalue está vinculado a una referencia a una clase base virtual (8.5.3), o
glvalue se utiliza como el operando de un dynamic_cast (5.2.7) o como el operando de typeid.
Entonces, ¿qué significa el acceso ? Eso se aclaró con el informe de defectos 1531 que define el acceso como:
acceso
Leer o modificar el valor de un objeto.
Así que fill
accede a un miembro de datos no estáticos y por lo tanto tenemos un comportamiento indefinido
Esto también está de acuerdo con la sección 12.7
que dice:
[...] Para formar un puntero a (o acceder al valor de) un miembro directo no estático de un objeto obj, la construcción de obj deberá haber comenzado y su destrucción no se habrá completado, de lo contrario, el cálculo del valor del puntero (o acceder al valor de miembro) da como resultado un comportamiento indefinido.
Ya que está utilizando una copia de todos modos, también podría crear una instancia de Datos dentro del fill
e inicializarla. El evitas tener que pasar d
.
Como lo señaló TC, es importante citar explícitamente los detalles sobre cuándo comienza la vida útil. De la sección 3.8
:
La vida útil de un objeto es una propiedad de tiempo de ejecución del objeto. Se dice que un objeto tiene una inicialización no trivial si es de una clase o tipo agregado y uno de sus miembros es iniciado por un constructor que no sea un constructor predeterminado trivial. [Nota: la inicialización por un constructor trivial de copiar / mover no es una inicialización trivial. - nota final] La vida útil de un objeto de tipo T comienza cuando:
se obtiene un almacenamiento con la alineación y tamaño adecuados para el tipo T, y
Si el objeto tiene una inicialización no trivial, su inicialización está completa.
La inicialización no es trivial ya que estamos inicializando a través del constructor de copia.
No veo un problema. El acceso a los miembros enteros sin inicializar es válido, ya que está accediendo con el propósito de escribir. Leerlos causaría UB.