Introdução: por que os construtores das classes pai são um campo minado para iniciantes
Todo mundo que começou a estudar herança em C++, mais cedo ou mais tarde, se deparou com a seguinte situação: você cria uma classe derivada, o compilador reclama, o programa não compila e você não entende o porquê. Na maioria das vezes, a raiz do problema está na chamada incorreta do construtor da classe base. Esta questão no Stack Overflow acumulou milhares de visualizações, e não é à toa: até mesmo desenvolvedores experientes às vezes esquecem regras básicas.
Neste artigo, vamos analisar as regras para chamar o construtor da classe base em C++, passar pelos 5 erros mais comuns e mostrar como evitá-los. Se você quer escrever código confiável sem surpresas — leia até o final.
Breve introdução: como os construtores funcionam na herança
Em C++, os construtores não são herdados. Quando você cria um objeto de uma classe derivada, primeiro o construtor da classe base é chamado (implícita ou explicitamente) e, em seguida, o construtor da classe derivada. Se você não especificar qual construtor da classe pai chamar, o compilador tentará chamar o construtor padrão da classe base. Se ele não existir, você receberá um erro de compilação.
A chamada explícita é feita através da lista de inicialização:
class Base {public: Base(int x) { /* ... */ }};
class Derived : public Base {public: // Correto: chamada explícita do construtor Base(int) Derived() : Base(42) { /* ... */ }};Parece simples? Mas é exatamente aqui que os iniciantes cometem 90% dos erros. Vamos analisá-los em ordem.
Top 5 erros ao chamar o construtor da classe base
Erro nº 1: Esqueceu de chamar o construtor da classe pai com parâmetros
O problema mais comum. Se a classe base não tem um construtor padrão e você não o chamou explicitamente — o compilador emitirá um erro.
class Base {public: Base(int x) { /* inicialização */ }};
class Derived : public Base {public: // ERRO: o compilador tenta chamar Base(), que não existe Derived() { }};Como corrigir:
class Derived : public Base {public: // CORRETO: chamamos explicitamente Base(int) Derived() : Base(0) { } // Ou passamos o parâmetro externamente: Derived(int x) : Base(x) { }};Erro nº 2: Ordem de inicialização incorreta
Muitos pensam que a ordem das chamadas dos construtores na lista de inicialização determina a ordem de execução. Isso não é verdade. A classe base é sempre inicializada primeiro, independentemente de onde você escreveu sua chamada.
class Derived : public Base {private: int value;public: // Parece que value é inicializado primeiro? Não! Derived() : value(10), Base(5) { } // Na verdade: primeiro Base(5), depois value(10)};Isso pode levar a erros lógicos se a inicialização de value depender do estado da classe base.
Erro nº 3: Chamar o construtor da classe pai dentro do corpo do construtor
Alguns iniciantes tentam chamar o construtor da classe base dentro das chaves, como um método comum. Isso não funciona — o construtor só pode ser chamado na lista de inicialização.
class Derived : public Base {public: Derived() { // ERRO: assim o construtor não é chamado Base(10); // Apenas cria um objeto temporário e o deleta }};Erro nº 4: Confusão com construtores de cópia
Ao implementar o construtor de cópia em uma classe derivada, é necessário chamar explicitamente o construtor de cópia da classe base. Caso contrário, o construtor padrão será chamado e os dados da classe base não serão copiados.
class Derived : public Base {public: // ERRO: Base será copiado incorretamente Derived(const Derived& other) { /* ... */ }
// CORRETO: Derived(const Derived&