Ir para o conteúdo

Última atualização:2026-10-12

Idiom de lista de inicialização de classes em C++

  • A inicialização de membros const só pode ser feita na lista de inicialização do construtor
  • A inicialização de membros de referência também só pode ser feita na lista de inicialização do construtor
  • A inicialização de membros-objeto (cuja classe correspondente não tem construtor padrão) também só pode ser feita na lista de inicialização do construtor

Aninhamento entre classes

Primeira forma: usar a lista de inicialização.

class Animal {
public:
    Animal() {
        std::cout << "Animal() is called" << std::endl;
    }

    Animal(const Animal &) {
        std::cout << "Animal (const Animal &) is called" << std::endl;
    }

    Animal &operator=(const Animal &) {
        std::cout << "Animal & operator=(const Animal &) is called" << std::endl;
        return *this;
    }

    ~Animal() {
        std::cout << "~Animal() is called" << std::endl;
    }
};

class Dog {
public:
    Dog(const Animal &animal) : __animal(animal) {
        std::cout << "Dog(const Animal &animal) is called" << std::endl;
    }

    ~Dog() {
        std::cout << "~Dog() is called" << std::endl;
    }

private:
    Animal __animal;
};

int main() {
    Animal animal;
    std::cout << std::endl;
    Dog d(animal);
    std::cout << std::endl;
    return 0;
}

Resultado da execução:

Animal() is called

Animal (const Animal &) is called
Dog(const Animal &animal) is called

~Dog() is called
~Animal() is called
~Animal() is called

Analisando de cima para baixo, em ordem:

No main, Animal animal; chama o construtor padrão.

Dog d(animal); equivale a:

Animal __animal = animal;
Na prática, é chamado o construtor de cópia e, por isso, é impresso:
Animal (const Animal &) is called
Em seguida, é impressa a saída dentro do construtor de Dog.

Por fim, o destrutor é chamado e o programa termina.

Segunda forma: inicializar o objeto por atribuição no construtor.

O construtor é modificado assim:

Dog(const Animal &animal) {
    __animal = animal;
    std::cout << "Dog(const Animal &animal) is called" << std::endl;
}
Neste caso, o resultado é:
Animal() is called

Animal() is called
Animal & operator=(const Animal &) is called
Dog(const Animal &animal) is called

~Dog() is called
~Animal() is called
~Animal() is called

Conclui-se então:

Ao chamar Dog d(animal);, equivale a:

definir primeiro o objeto e só depois atribuir; por isso, chama-se primeiro o construtor padrão e, em seguida, a função de sobrecarga do operador = .

// 假设之前已经有了animal对象
Animal __animal;
__animal = animal;

Resumo

Com o exposto acima, chegamos às seguintes conclusões:

  • Quando a classe contém outra class ou struct personalizada, usar a lista de inicialização significa, na prática, criar o objeto e inicializá-lo ao mesmo tempo
  • Já a atribuição dentro da classe equivale a definir primeiro o objeto e atribuir depois: normalmente chama-se primeiro o construtor padrão e, então, a função de sobrecarga do operador =.

Relação de herança sem construtor padrão

Agora considere mudar a relação acima para herança e modificar os construtores de Animal e Dog, conforme o código abaixo:

class Animal {
public:
    Animal(int age) {
        std::cout << "Animal(int age) is called" << std::endl;
    }

    Animal(const Animal & animal) {
        std::cout << "Animal (const Animal &) is called" << std::endl;
    }

    Animal &operator=(const Animal & amimal) {
        std::cout << "Animal & operator=(const Animal &) is called" << std::endl;
        return *this;
    }

    ~Animal() {
        std::cout << "~Animal() is called" << std::endl;
    }
};

class Dog : Animal {
public:
    Dog(int age) : Animal(age) {
        std::cout << "Dog(int age) is called" << std::endl;
    }

    ~Dog() {
        std::cout << "~Dog() is called" << std::endl;
    }

};

O acima passa parâmetros ao construtor parametrizado da classe base por meio da lista de inicialização; se não os passarmos pela lista de inicialização, que impacto haverá?

Removendo a lista de inicialização

Dog(int age)  {
    std::cout << "Dog(int age) is called" << std::endl;
}
Executando o programa:

error: no matching function for call to ‘Animal::Animal()’

Como Animal não tem construtor padrão, dá erro; deparar-se com esse tipo de problema é catastrófico e devemos evitá-lo ao máximo: pode-se inicializar o construtor da classe base por meio da lista de inicialização.

Membros de dados const e membros de dados de referência na classe

Em particular, os membros de dados de referência devem ser inicializados com a lista de inicialização, e não por atribuição!

Por exemplo: adicionar membros privados ao Animal acima e modificar o construtor:

class Animal {
public:
    Animal(int age,std::string name) {
        std::cout << "Animal(int age) is called" << std::endl;
    }
private:
    int &age_;
    const std::string name_;
};

Reporta o erro abaixo:

error: uninitialized reference member in ‘int&’

Deveria ser alterado para:

Animal(int age, std::string name) : age_(age), name_(name) {
    std::cout << "Animal(int age) is called" << std::endl;
}

Tópicos relacionados

Comentários