c++ variable Inicialización del valor: ¿inicialización por defecto o inicialización cero?



variables globales c++ (1)

He gray_code clase de gray_code que está destinada a almacenar algunos enteros sin signo cuyos bits subyacentes se almacenan en el orden del código gris. Aquí está:

template<typename UnsignedInt>
struct gray_code
{
    static_assert(std::is_unsigned<UnsignedInt>::value,
                  "gray code only supports built-in unsigned integers");

    // Variable containing the gray code
    UnsignedInt value;

    // Default constructor
    constexpr gray_code()
        = default;

    // Construction from UnsignedInt
    constexpr explicit gray_code(UnsignedInt value):
        value( (value >> 1) ^ value )
    {}

    // Other methods...
};

En algún algoritmo genérico, escribí algo como esto:

template<typename UnsignedInt>
void foo( /* ... */ )
{
    gray_code<UnsignedInt> bar{};
    // Other stuff...
}

En este fragmento de código, esperaba que la bar estuviera inicializada en cero y, por bar.value tanto, que el bar.value de bar.value fuera inicializado en cero. Sin embargo, después de haber luchado con errores inesperados, parece que bar.value se inicializa con basura (4606858 para ser exactos) en lugar de 0u . Eso me sorprendió, así que fui a cppreference.com para ver qué debía hacer exactamente la línea anterior ...

De lo que puedo leer, la forma T object{}; Corresponde a la inicialización del valor . Me pareció interesante esta cita:

En todos los casos, si se utiliza el par vacío de llaves {} y T es un tipo agregado, se realiza una inicialización agregada en lugar de una inicialización de valor.

Sin embargo, gray_code tiene un constructor proporcionado por el usuario. Por lo tanto, no es un agregado, por lo que no se realiza la inicialización del agregado . gray_code no tiene un constructor que tome un std::initializer_list por lo que la inicialización de la lista tampoco se realiza. El valor inicializado de gray_code debe seguir las reglas usuales de C ++ 14 para la inicialización de valores:

1) Si T es un tipo de clase sin constructor predeterminado o con un constructor predeterminado proporcionado por el usuario o con un constructor predeterminado eliminado, el objeto se inicializa por defecto.

2) Si T es un tipo de clase sin un constructor predeterminado provisto o eliminado por el usuario (es decir, puede ser una clase con un constructor predeterminado predeterminado o con uno implícitamente definido), entonces el objeto tiene una inicialización cero y luego es predeterminado-inicializado si tiene un constructor predeterminado no trivial.

3) Si T es un tipo de matriz, cada elemento de la matriz tiene un valor inicializado.

4) De lo contrario, el objeto es cero inicializado.

Si leo correctamente, gray_code tiene un constructor predeterminado explícitamente predeterminado (no proporcionado por el usuario), por lo tanto 1) no se aplica. Tiene un constructor predeterminado predeterminado, por lo que 2) se aplica: gray_code se zero-initialized . El constructor predeterminado predeterminado parece cumplir con todos los requisitos de un constructor predeterminado trivial, por lo que la inicialización predeterminada no debería ocurrir. Echemos un vistazo a continuación, cómo gray_code es cero inicializado:

  • Si T es un tipo escalar, el valor inicial del objeto es la constante integral cero convertida implícitamente en T.

  • Si T es un tipo de clase no sindicalizada, todas las clases base y los miembros de datos no estáticos se inicializan en cero, y todo el relleno se inicializa en cero bits. Los constructores, si los hay, son ignorados.

  • Si T es un tipo de unión, el primer miembro de datos con nombre no estático se inicializa con cero y todo el relleno se inicializa en cero bits.

  • Si T es un tipo de matriz, cada elemento se inicializa con cero

  • Si T es el tipo de referencia, no se hace nada.

gray_code es un tipo de clase no sindical. Por lo tanto, todos sus miembros de datos no estáticos deben inicializarse, lo que significa que el value se inicializa en cero. value satisface std::is_unsigned y, por lo tanto, es un tipo escalar, lo que significa que se debe inicializar con "la constante integral cero convertida implícitamente a T".

Entonces, si leo correctamente todo eso, en la función foo anterior, bar.value siempre debe inicializarse con 0 y nunca debe inicializarse con basura, ¿verdad?

Nota: el compilador con el que compilé mi código es MinGW_w4 GCC 4.9.1 con (subprocesos POSIX y excepciones enanas) en caso de que eso ayude. Aunque a veces consigo basura en mi computadora, nunca logré obtener nada más que cero con los compiladores en línea.

Actualización: Parece ser un error de GCC que el error es mío y no de mi compilador. En realidad, al escribir esta pregunta, asumí por simplicidad que

class foo {
    foo() = default;
};

y

class foo {
    foo();
};

foo::foo() = default;

eran equivalentes. Ellos no son. Aquí está la cita del estándar C ++ 14, sección [dcl.fct.def.default]:

Una función es provista por el usuario si es declarada por el usuario y no está predeterminada por defecto o eliminada en su primera declaración.

En otras palabras, cuando obtuve valores de basura, mi constructor predeterminado predeterminado fue proporcionado por el usuario ya que no fue explícitamente omitido en su primera declaración. Por lo tanto, lo que sucedió no fue una inicialización cero sino una inicialización predeterminada. Gracias de nuevo a @Columbo por señalar el problema real.


Entonces, si leo correctamente todo eso, en la función foo anterior, bar.value siempre debe inicializarse con 0 y nunca debe inicializarse con basura, ¿verdad?

Sí. Su objeto se inicializa directamente en la lista. * [Dcl.init.list] / 3 de C ++ 14 especifica que

La inicialización de lista de un objeto o referencia de tipo T se define de la siguiente manera:

  • [... puntos de bala no aplicables ...]

  • De lo contrario, si T es un agregado, se realiza la inicialización agregada (8.5.1).

  • De lo contrario, si la lista de inicializadores no tiene elementos y T es un tipo de clase con un constructor predeterminado, el objeto se inicializa con valores.

  • […]

Su clase no es un agregado ya que tiene constructores proporcionados por el usuario, pero tiene un constructor predeterminado. [dcl.init] / 7:

Para inicializar con valor un objeto de tipo T significa:

  • si T es un tipo de clase (posiblemente calificado para cv) (Cláusula 9) sin un constructor predeterminado (12.1) o un constructor predeterminado que sea proporcionado o eliminado por el usuario , entonces el objeto se inicializa por defecto;

  • si T es un tipo de clase (posiblemente cv calificado) sin un constructor predeterminado proporcionado o eliminado por el usuario , entonces el objeto se inicializa con cero y se comprueban las restricciones semánticas para la inicialización predeterminada, y si T tiene un constructor predeterminado no trivial , el objeto está inicializado por defecto;

[dcl.fct.def.default] / 4:

Una función miembro especial es provista por el usuario si es declarada por el usuario y no está predeterminada por defecto [...] en su primera declaración.

Por lo tanto, su constructor no es proporcionado por el usuario, por lo tanto, el objeto tiene cero inicialización. (El constructor no es llamado ya que es trivial).

Y finalmente, en caso de que esto no fuera claro, inicializar en cero un objeto o referencia de tipo T significa:

  • si T es un tipo escalar (3.9), el objeto se inicializa al valor obtenido al convertir el literal entero 0 (cero) a T ;

  • si T es un tipo de clase no sindicalizada (posiblemente cv calificado), cada miembro de datos no estáticos y cada subobjeto de clase base se inicializan con cero y el relleno se inicializa a cero bits;

  • […]


Por lo tanto tampoco

  • Su compilador está fastidiado

  • ... o su código desencadena un comportamiento indefinido en algún otro punto.

* La respuesta sigue siendo sí en C ++ 11, aunque las secciones citadas no son equivalentes.





c++14