1 pontos por GN⁺ 2024-07-06 | 1 comentários | Compartilhar no WhatsApp

Preciso inicializar sem construtor

  • Introdução

    • Ao aprender C++ pela primeira vez, você descobre os casos em que o compilador fornece um construtor padrão.
    • Isso leva à preocupação com o risco de um objeto não ser inicializado em certas situações.
  • Inicialização padrão e inicialização por valor

    • T t; realiza inicialização padrão.
      • Se T for um tipo de classe e tiver construtor padrão, ele será executado.
      • Se T for um tipo de array, cada elemento será inicializado por padrão.
      • Caso contrário, nada acontece.
    • T t{}; realiza inicialização por valor.
      • Se T for um tipo de classe, faz inicialização padrão quando não houver construtor padrão ou quando houver um construtor padrão fornecido pelo usuário ou deletado.
      • Caso contrário, primeiro inicializa com zero e depois faz inicialização padrão.
      • Se T for um tipo de array, cada elemento será inicializado por valor.
      • Caso contrário, inicializa com zero.
  • Construtor padrão

    • Se você não declarar um construtor padrão, o compilador declara um construtor padrão implicitamente.
    • O construtor padrão declarado implicitamente tem corpo vazio e lista de inicialização de membros vazia.
    • Exemplo:
      struct T {
        int x;
        T() = default;
      };
      T t{};
      std::cout << t.x << std::endl; // resultado da saída é 0
      
  • Construtor padrão definido implicitamente

    • Se o construtor padrão for declarado implicitamente ou declarado explicitamente como default, o compilador fornece um construtor padrão definido implicitamente.
    • Exemplo:
      struct T {
        T();
      };
      T::T() = default;
      T t{};
      std::cout << t.x << std::endl; // resultado da saída é valor lixo
      
  • Casos em que não é possível fornecer um construtor padrão

    • Quando T tem um membro de referência não estático
    • Quando T tem um membro não estático ou uma classe base não abstrata que não pode ser construída por padrão ou destruída
    • Quando T tem um membro const não estático sem inicializador de membro padrão
  • Inicialização correta

    • T t{}; realiza inicialização por lista.
    • A inicialização por lista se divide em inicialização direta por lista e inicialização por cópia com lista.
    • Exemplo:
      struct S {
        int a;
        float b;
        char c;
      };
      S s{3, 4.0f, 'S'}; // nenhuma chamada de construtor
      
  • Inicialização por lista e inicialização de agregados

    • A inicialização de agregados é uma forma especial de inicialização por lista, em que cada elemento da classe ou do array é inicializado por cópia a partir de cada elemento da lista de inicialização.
    • Exemplo:
      struct A {
        const int x;
      };
      A a{}; // a.x é inicializado com 0
      
  • Inicialização com parênteses

    • A inicialização com parênteses realiza inicialização direta sem lista.
    • Exemplo:
      struct T {
        const int& r;
      };
      T t(42); // t.r é uma referência para 42
      
  • Resumo

    • As regras de inicialização são complexas, mas escrever construtores manualmente evita a maioria dos problemas.
    • Em vez de deixar isso para o compilador, é melhor escrever o construtor diretamente.

Opinião do GN⁺

  • Este texto explica bem a complexidade das regras de inicialização em C++.
  • Entender as regras de inicialização em C++ é importante, pois isso afeta bastante a estabilidade e o desempenho do código.
  • Escrever construtores diretamente é a melhor forma de evitar problemas de inicialização.
  • Uma linguagem com funcionalidade semelhante é Rust, que tem regras de inicialização mais claras.
  • Ao adotar novas tecnologias, é importante compreender e usar corretamente detalhes como as regras de inicialização.

1 comentários

 
GN⁺ 2024-07-06
Comentários no Hacker News
  • O resultado da inicialização de t será 0

    • Isso ocorre porque t é inicializado por valor e, como T não tem um construtor padrão definido pelo usuário, o objeto é zerado antes de o construtor padrão ser chamado
  • O construtor padrão inicializa os membros por padrão, o que é diferente de inicialização por valor

  • O GCC parece concordar com isso

  • O autor deixou passar que na verdade está inicializando x por valor

    • O resultado sai diferente do esperado
  • Os detalhes das regras são complexos e às vezes têm partes irracionais

    • Mas, na maioria dos casos, é possível obter o resultado esperado
  • Tornar a inicialização padrão explicitamente possível seria uma grande melhoria

    • Como a inicialização por valor é comum, é preciso escrever comentários quando se quer inicialização padrão
    • Uma sintaxe como std::array<int, 100> = void; seria melhor
  • A ligação entre inicialização por lista e inicialização de agregados é que, quando a inicialização por lista é feita sobre um agregado, a inicialização de agregado é realizada

    • Porém, se a lista tiver apenas um argumento, é feita inicialização direta
  • O caso de um elemento funciona de forma diferente de dois ou mais elementos

    • Isso acontece em uma linguagem em que criar structs a partir de parameter packs está ficando cada vez mais simples
  • É possível escrever o próprio construtor e inicializar uma tupla ou array com apenas um elemento fornecido

    • Mas, em casos especiais, o construtor errado pode ser chamado
  • Quando a lista de inicialização do C++11 apareceu pela primeira vez, descobrir isso pareceu loucura

  • Menção a "I Have No Mouth, and I Must Scream" (1967)

  • Uso da sintaxe T::T() = default;

  • Espera-se que a saída seja 0, mas na prática sairá um valor lixo

    • Nem tudo pode ser perfeito
  • Permite que consumidores da biblioteca alterem o comportamento da biblioteca

  • Se quiser ainda mais complexidade de C++, a recomendação é o C++ FQA

    • Já se passaram 15 anos, mas ele continua válido porque o C++ quase nunca remove recursos ou comportamentos antigos
  • O tema do blog foi inspirado em computadores da era DEC, mas é limpo e minimalista

    • Refrescante
  • Ler isso dá uma sensação de tontura

    • Lembra de quando se tentava entender construtores em Java e inicialização de objetos
  • Go e Rust não têm construtores especiais, o que simplifica muita coisa

    • Fica a curiosidade se alguém já sentiu falta de construtores depois de parar de usá-los
  • Fica a curiosidade se existe alguma ferramenta de C++ que mostre todos os comportamentos implícitos

    • Por exemplo, todos os construtores adicionados, construtores de cópia implícitos etc.
  • Há informação incorreta sobre a classe

    • Se um construtor for declarado, o construtor padrão não é fornecido, e a inicialização padrão falha com diagnóstico do compilador
  • A afirmação de que "T t; não faz nada" está errada

    • No código de exemplo, T t; falha
  • O cabeçalho do blog tem um painel frontal da DEC