Reutilizando uma classe ao estendê-la
Você construiu classes com construtores e as limpou com destrutores. A herança é o próximo passo: em vez de copiar os membros de uma classe para outra, você declara que uma nova classe é uma versão especializada de uma existente e deixa que ela herde tudo automaticamente.
A classe existente é a classe base (ou pai); a nova é a classe derivada (ou filha). Uma classe derivada começa com todos os dados e comportamentos da base e, depois, adiciona ou altera o que a torna diferente. Esta é a principal ferramenta do C++ para a relação "é-um": um Dog é um Animal, uma SavingsAccount é uma BankAccount.
Sintaxe básica: class Derived : public Base
Você herda escrevendo, após o nome da classe derivada, dois-pontos, um especificador de acesso e o nome da classe base. A forma mais comum é a herança public.
Dog nunca declara name nem eat(), e ainda assim ambos funcionam em um objeto Dog porque foram herdados de Animal. A classe derivada é livre para adicionar membros como bark() dos quais a base nada sabe.
protected: membros somente para as filhas
Um membro private da base não é acessível dentro da classe derivada: a herança não quebra o encapsulamento. Quando você quer que as entranhas da base fiquem ocultas do mundo externo, mas disponíveis para as subclasses, use o especificador de acesso protected.
Pense nos três níveis como anéis concêntricos: private é "apenas esta classe", protected é "esta classe e seus descendentes" e public é "todo mundo". Veja especificadores de acesso para o panorama completo.
Ordem de construtores e destrutores
Um objeto derivado contém um subobjeto base, e essa parte base precisa estar viva antes de a parte derivada ser construída. Por isso a construção é executada da base primeiro e a destruição do derivado primeiro (a ordem exatamente inversa). Se a base precisar de argumentos de construtor, você os passa por meio da lista de inicialização de membros.
A saída torna a ordem concreta:
Animal ctor: Rex // a base é construída primeiro
Dog ctor // depois a parte derivada
Dog dtor // destruída na ordem inversa...
Animal dtor: Rex // ...a base por último
Se você esquecer o : Animal(n) e a base não tiver construtor padrão, o código não compilará: o C++ não tem ideia de como construir a parte base. Uma classe base da qual você pretende herdar deve quase sempre declarar um destrutor (e, como mostra a próxima página, muitas vezes um virtual).
Sobrescrita: redefinindo um método base
Uma classe derivada pode substituir um método herdado declarando um com a mesma assinatura. Você ainda pode alcançar o original por meio de Base::method().
Isso é simples ocultação de nomes, não polimorfismo: qual describe() é executado é decidido em tempo de compilação pelo tipo estático da variável. Essa é uma limitação crucial: se você chamar por meio de um Shape& ou Shape* que na verdade aponta para um Circle, ainda obterá Shape::describe(). Corrigir isso exige virtual, que é o tema da próxima página.
Cuidado com o fatiamento de objetos (slicing)
Como uma referência ou um ponteiro de base podem se referir a um objeto derivado, é tentador copiar um objeto derivado para uma variável de tipo base. Não faça isso: a parte derivada é fatiada para fora.
a é um Animal genuíno, não um Dog usando um rótulo de base, então breed simplesmente não existe nele. Para trabalhar com objetos derivados de forma polimórfica você precisa usar uma referência de base (Animal&) ou um ponteiro (Animal*), nunca um valor de base. O slicing é silencioso: compila sem erros e simplesmente descarta dados de forma discreta, o que o torna um dos bugs de herança mais fáceis de mandar para produção.
Erros comuns a evitar
- Esperar que membros
privateda base sejam acessíveis na filha. Não são. Useprotectedpara os dados de que a classe derivada legitimamente precisa e mantenha o estado verdadeiramente interno comoprivate. - Esquecer de encaminhar os argumentos do construtor base. Se a base não tiver construtor padrão, você precisa chamá-lo explicitamente na lista de inicialização do construtor derivado (
: Base(args)). - Fatiar um objeto derivado para um valor base. Copiar um
Dogpara umAnimaldescarta tudo que é específico doDog. Em vez disso, passe e armazene referências ou ponteiros de base. - Usar herança em excesso para reutilizar código. A herança modela "é-um". Se a relação for na verdade "tem-um" (um
Cartem umEngine), prefira composição - um objeto membro - em vez de herdar.
Próximo: Funções virtuais
A sobrescrita que você acabou de ver foi resolvida em tempo de compilação, então chamar por meio de um ponteiro base ignorava a versão derivada. A próxima página, funções virtuais, apresenta a palavra-chave virtual e override: o mecanismo que faz o tipo em tempo de execução decidir qual método é executado, desbloqueando o verdadeiro polimorfismo e explicando por que classes base precisam de destrutores virtuais.
Perguntas frequentes
O que é herança em C++?
A herança permite que você defina uma nova classe (a classe derivada) com base em uma já existente (a classe base). A classe derivada recebe automaticamente os membros de dados e as funções membro da base, e pode adicionar novos ou substituir comportamentos existentes. Ela modela uma relação "é-um" - um Dog é um Animal - e é a principal forma como o C++ reutiliza e estende código por hierarquias de classes.
Qual é a diferença entre herança pública e privada em C++?
Com a herança public (class Dog : public Animal), a interface pública da base continua pública na classe derivada, então um Dog é-um Animal e pode ser usado onde quer que se espere um Animal. Com a herança private, os membros herdados tornam-se privados: a classe derivada reutiliza a implementação da base, mas não é substituível por ela. A herança pública é de longe o caso comum; recorra à privada apenas para reutilização do tipo "implementado-em-termos-de".
Em que ordem os construtores e destrutores são executados com herança em C++?
Os construtores são executados da base primeiro: a classe base é totalmente construída antes de o corpo do construtor derivado ser executado. Os destrutores são executados na ordem exatamente inversa: primeiro o derivado e depois a base. Isso garante que, enquanto um objeto derivado está sendo montado ou desmontado, cada parte da qual ele depende já existe (ou ainda existe).