1 puntos por GN⁺ 2024-07-06 | 1 comentarios | Compartir por WhatsApp

Necesito inicializar y no hay constructor

  • Introducción

    • Cuando aprendí C++ por primera vez, aprendí en qué casos el compilador proporciona un constructor por defecto.
    • Eso me llevó a pensar en el riesgo de que un objeto pueda quedar sin inicializar en ciertas situaciones.
  • Inicialización por defecto e inicialización por valor

    • T t; realiza inicialización por defecto.
      • Si T es un tipo de clase y tiene constructor por defecto, este se ejecuta.
      • Si T es un tipo arreglo, cada elemento se inicializa por defecto.
      • En caso contrario, no hace nada.
    • T t{}; realiza inicialización por valor.
      • Si T es un tipo de clase, se inicializa por defecto si no tiene constructor por defecto o si tiene un constructor por defecto proporcionado por el usuario o eliminado.
      • En caso contrario, primero se inicializa en 0 y luego se inicializa por defecto.
      • Si T es un tipo arreglo, cada elemento se inicializa por valor.
      • En caso contrario, se inicializa en 0.
  • Constructor por defecto

    • Si no se declara un constructor por defecto, el compilador lo declara implícitamente.
    • El constructor por defecto declarado implícitamente tiene un cuerpo vacío y una lista de inicialización de miembros vacía.
    • Ejemplo:
      struct T {
        int x;
        T() = default;
      };
      T t{};
      std::cout << t.x << std::endl; // el resultado es 0
      
  • Constructor por defecto definido implícitamente

    • Si el constructor por defecto se declara implícitamente o se declara explícitamente como predeterminado, el compilador proporciona un constructor por defecto definido implícitamente.
    • Ejemplo:
      struct T {
        T();
      };
      T::T() = default;
      T t{};
      std::cout << t.x << std::endl; // el resultado es un valor basura
      
  • Casos en los que no se puede proporcionar un constructor por defecto

    • Cuando T tiene un miembro de referencia no estático
    • Cuando T tiene un miembro no estático o una clase base no abstracta que no puede construirse por defecto o destruirse
    • Cuando T tiene un miembro const no estático sin inicializador de miembro por defecto
  • Inicialización correcta

    • T t{}; realiza inicialización por lista.
    • La inicialización por lista se divide en inicialización directa por lista e inicialización por copia con lista.
    • Ejemplo:
      struct S {
        int a;
        float b;
        char c;
      };
      S s{3, 4.0f, 'S'}; // no se llama a ningún constructor
      
  • Inicialización por lista e inicialización de agregados

    • La inicialización de agregados es una forma especial de inicialización por lista, en la que cada elemento de una clase o arreglo se inicializa por copia con cada elemento de la lista de inicialización.
    • Ejemplo:
      struct A {
        const int x;
      };
      A a{}; // a.x se inicializa en 0
      
  • Inicialización con paréntesis

    • La inicialización con paréntesis realiza inicialización directa no basada en lista.
    • Ejemplo:
      struct T {
        const int& r;
      };
      T t(42); // t.r es una referencia que apunta a 42
      
  • Resumen

    • Las reglas de inicialización son complejas, pero escribir constructores directamente permite evitar la mayoría de los problemas.
    • Es mejor no dejarlo en manos del compilador y escribir los constructores uno mismo.

Opinión de GN⁺

  • Este artículo explica bien la complejidad de las reglas de inicialización en C++.
  • Entender las reglas de inicialización de C++ es importante, ya que tienen un gran impacto en la estabilidad y el rendimiento del código.
  • Escribir constructores directamente es la mejor forma de evitar problemas de inicialización.
  • Un lenguaje con funcionalidades similares es Rust, y en Rust las reglas de inicialización son más claras.
  • Al adoptar nuevas tecnologías, es importante comprender y usar bien detalles como las reglas de inicialización.

1 comentarios

 
GN⁺ 2024-07-06
Comentarios de Hacker News
  • El resultado de inicializar t será 0

    • Esto se debe a que t se inicializa por valor y, como T no tiene un constructor predeterminado definido por el usuario, el objeto se inicializa a 0 y luego se llama al constructor predeterminado
  • El constructor predeterminado inicializa los miembros de forma predeterminada, lo cual es distinto de la inicialización por valor

  • GCC parece estar de acuerdo con esto

  • El autor pasó por alto que en realidad está inicializando x por valor

    • El resultado sale distinto de lo esperado
  • Los detalles de las reglas son complejos y a veces tienen partes poco razonables

    • Sin embargo, en la mayoría de los casos se puede obtener el resultado esperado
  • Hacer explícita la inicialización predeterminada sería una gran mejora

    • Como la inicialización por valor es común, hay que escribir comentarios cuando se quiere la inicialización predeterminada
    • Una sintaxis como std::array<int, 100> = void; sería mejor
  • El vínculo entre la inicialización por lista y la inicialización de agregados es que, cuando la inicialización por lista se realiza sobre un agregado, se lleva a cabo la inicialización de agregados

    • Sin embargo, si la lista tiene solo un argumento, se realiza inicialización directa
  • El caso de un solo elemento funciona distinto al de dos o más elementos

    • Esto ocurre en un lenguaje donde crear structs desde un parameter pack se vuelve cada vez más simple
  • Uno puede escribir su propio constructor e inicializar una tupla o arreglo al que se le proporcione un solo elemento

    • Pero en casos especiales podría llamarse al constructor equivocado
  • Cuando aparecieron por primera vez las listas de inicialización de C++11, alguien notó esto y pensó que era una locura

  • Se menciona "I Have No Mouth, and I Must Scream" (1967)

  • Uso de la sintaxis T::T() = default;

  • Se espera que la salida sea 0, pero en realidad aparecerá un valor basura

    • Algunas cosas no pueden ser perfectas
  • Permite que quien consume la biblioteca pueda cambiar el comportamiento de la biblioteca

  • Si se quiere más complejidad de C++, se recomienda C++ FQA

    • Han pasado 15 años, pero sigue vigente porque C++ casi no elimina características o comportamientos antiguos
  • El tema del blog está inspirado en computadoras de la era DEC, pero se ve limpio y minimalista

    • Se siente fresco
  • Leer esto provoca mareo

    • Hace recordar cuando se intentaba entender los constructores de Java y la inicialización de objetos
  • Go y Rust no tienen constructores especiales, lo que simplifica muchas cosas

    • Da curiosidad saber si alguien ha extrañado los constructores después de dejar de usarlos
  • Da curiosidad si existe alguna herramienta de C++ que muestre todos los comportamientos implícitos

    • Por ejemplo, todos los constructores agregados, constructores de copia implícitos, etc.
  • Se da información incorrecta sobre la clase

    • Si se declara un constructor, no se proporciona un constructor predeterminado y la inicialización predeterminada falla con un diagnóstico del compilador
  • La afirmación de que T t; "no hace nada" es incorrecta

    • En el código de ejemplo, T t; falla
  • En el encabezado del blog hay un panel frontal de DEC