C++ Herança

A lojinha da Ana vende canetas, cadernos e livros. Todo produto tem nome e preço; mas o livro tem também um autor. Você poderia copiar a classe Produto inteira, colar com o nome Livro e acrescentar o autor. Funciona, até o dia em que você corrige um erro no cálculo do desconto em uma e esquece da outra. A herança resolve isso: você diz "um Livro é um Produto, com algo a mais", e a classe nova recebe tudo da antiga, sem copiar uma linha.

Você vai aprender
  • Como criar uma classe a partir de outra: class Livro : public Produto.
  • O que a classe filha herda, o que ela acrescenta e o que ela não pode tocar.
  • Como chamar o construtor da classe base na lista de inicialização.
  • O nível de acesso protected, entre o public e o private.
  • A ordem real em que as partes nascem e morrem.
  • Como redefinir um método e ainda usar a versão da base (Produto::mostrar()).
  • A ideia da herança pública × privada, e como proibir a herança com final.

Antes, é bom conhecer Classes e Objetos, Construtores e public e private. Os programas foram compilados com o g++ 15 na máquina da Ana, na pasta ~/cpp, com g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa.


A ideia: "é um"

Na escola você aprendeu que "todo cachorro é um mamífero": ele tem tudo o que um mamífero tem (respira, mama quando filhote) e mais algumas coisas só dele (late). Herança é isso em código:

NomeTambém chamada deNo exemplo
Classe baseclasse mãe, superclasseProduto: nome, preço, desconto
Classe derivadaclasse filha, subclasseLivro: tudo do Produto + autor

Antes de usar herança, faça o teste do "é um": a frase "um Livro é um Produto" faz sentido? Sim: herança serve. "Um Carro é um Motor"? Não: o carro tem um motor. Nesse caso, o motor vira um atributo do carro (isso se chama composição), e não uma classe base.


1. A primeira herança

#include <iostream>
#include <string>

class Produto {
public:
    Produto(std::string nome, double preco) : nome_{nome}, preco_{preco} {}

    std::string nome() const { return nome_; }
    double preco() const { return preco_; }
    void aplicar_desconto(double pct) { preco_ -= preco_ * pct / 100; }
    void mostrar() const {
        std::cout << nome_ << " — R$ " << preco_ << '\n';
    }
private:
    std::string nome_;
    double preco_;
};

class Livro : public Produto {
public:
    Livro(std::string nome, double preco, std::string autor)
        : Produto{nome, preco}, autor_{autor} {}

    std::string autor() const { return autor_; }
private:
    std::string autor_;
};

int main() {
    Produto caneta{"Caneta azul", 3.5};
    Livro livro{"Dom Casmurro", 40, "Machado de Assis"};

    caneta.mostrar();
    livro.mostrar();                       // herdado de Produto
    livro.aplicar_desconto(10);            // herdado de Produto
    std::cout << livro.nome() << ", de " << livro.autor()
              << ", agora custa R$ " << livro.preco() << '\n';
}
class Livro
Uma classe nova, como qualquer outra.
:
Os dois-pontos depois do nome querem dizer "derivada de".
public
O tipo de herança: o que era public no Produto continua public no Livro. É o que você vai usar em 99% dos casos (a seção 6 mostra a outra opção).
Produto
A classe base.
: Produto{nome, preco}
Na lista de inicialização do construtor do Livro: chama o construtor do Produto, para montar a "parte produto" do livro.
autor_{autor}
Depois, inicializa o que é só do livro.
std::string autor() const
Um método novo, que só o Livro tem.
$ g++ -std=c++23 -Wall -Wextra livro.cpp -o livro
$ ./livro
Caneta azul — R$ 3.5
Dom Casmurro — R$ 40
Dom Casmurro, de Machado de Assis, agora custa R$ 36

A classe Livro tem só um atributo e um método escritos nela, mas o objeto livro usou mostrar(), aplicar_desconto(), nome() e preco(), todos herdados. Por dentro, todo Livro carrega um Produto completo, com mais um pedaço no fim:

┌─────────── Livro ────────────┐
│ ┌──────── Produto ─────────┐ │
│ │ nome_  = "Dom Casmurro"  │ │
│ │ preco_ = 36              │ │
│ └──────────────────────────┘ │
│ autor_ = "Machado de Assis"  │
└──────────────────────────────┘

Experimente: rode como está. Depois chame livro.aplicar_desconto(50); e mostre de novo. Para ir além, crie uma classe Caderno : public Produto com um atributo int folhas_ e um método folhas().

#include <iostream>
#include <string>

class Produto {
public:
    Produto(std::string nome, double preco) : nome_{nome}, preco_{preco} {}

    std::string nome() const { return nome_; }
    double preco() const { return preco_; }
    void aplicar_desconto(double pct) { preco_ -= preco_ * pct / 100; }
    void mostrar() const {
        std::cout << nome_ << " — R$ " << preco_ << '\n';
    }
private:
    std::string nome_;
    double preco_;
};

class Livro : public Produto {
public:
    Livro(std::string nome, double preco, std::string autor)
        : Produto{nome, preco}, autor_{autor} {}

    std::string autor() const { return autor_; }
private:
    std::string autor_;
};

int main() {
    Livro livro{"Dom Casmurro", 40, "Machado de Assis"};
    livro.mostrar();
    std::cout << "Autor: " << livro.autor() << '\n';
}

2. O construtor da classe base

Construtores não são herdados como os outros métodos: o Livro precisa do seu próprio. E ele tem a obrigação de construir a parte Produto que carrega por dentro. O lugar para isso é a lista de inicialização, antes dos atributos:

Livro(std::string nome, double preco, std::string autor)
    : Produto{nome, preco},   // 1º: a parte Produto
      autor_{autor}           // 2º: o que é só do Livro
{}

Se você esquecer o Produto{…}, o C++ tenta usar um construtor sem parâmetros do Produto. Como ele não existe, o g++ reclama (o erro completo está em Quando dá errado):

$ g++ -std=c++23 -Wall -Wextra h3.cpp -o h3
h3.cpp: In constructor ‘Livro::Livro(std::string, double, std::string)’:
h3.cpp:15:23: error: no matching function for call to ‘Produto::Produto()’
   15 |         : autor_{autor} {}
      |                       ^
Atalho: herdar os construtores C++11

Se a classe filha não acrescenta atributos que precisem de valor, ela pode reaproveitar os construtores da base com uma linha: using Produto::Produto;. Aí Brinde b{"Chaveiro", 0}; chama direto o construtor do Produto.


3. O que a filha pode tocar: protected

A classe Livro herdou preco_, mas ele é private no Produto. Privado quer dizer "só a própria classe", e a filha é outra classe. Tente usar preco_ num método do Livro:

class Livro : public Produto {
public:
    // ... construtor
    void etiqueta() const {
        std::cout << nome() << " (" << autor_ << ") R$ " << preco_ << '\n';
    }
private:
    std::string autor_;
};
$ g++ -std=c++23 -Wall -Wextra h1.cpp -o h1
h1.cpp: In member function ‘void Livro::etiqueta() const’:
h1.cpp:19:61: error: ‘double Produto::preco_’ is private within this context
   19 |         std::cout << nome() << " (" << autor_ << ") R$ " << preco_ << '\n';
      |                                                             ^~~~~~
h1.cpp:10:12: note: declared private here
   10 |     double preco_;
      |            ^~~~~~

"Produto::preco_ é privado neste contexto". A parte Produto existe dentro do livro, mas o código do Livro não pode mexer nela diretamente. Há dois caminhos:

  1. Usar os métodos públicos da base: preco() em vez de preco_. É o caminho mais seguro, porque o Produto continua no controle das próprias regras.
  2. Marcar o atributo como protected ("protegido"): fica invisível para o resto do programa, mas visível para as classes filhas.
class Produto {
public:
    Produto(std::string nome, double preco) : nome_{nome}, preco_{preco} {}
    std::string nome() const { return nome_; }
private:
    std::string nome_;
protected:
    double preco_;          // filhas podem usar; o main, não
};

Com isso, etiqueta() compila. Mas o main continua barrado:

$ g++ -std=c++23 -Wall -Wextra h2.cpp -o h2
h2.cpp: In function ‘int main()’:
h2.cpp:29:11: error: ‘double Produto::preco_’ is protected within this context
   29 |     livro.preco_ = 0;
      |           ^~~~~~
h2.cpp:11:12: note: declared protected here
   11 |     double preco_;
      |            ^~~~~~
AcessoA própria classeClasses filhasResto do programa
publicsimsimsim
protectedsimsimnão
privatesimnãonão
Use protected com parcimônia

Um atributo protected pode ser alterado por qualquer classe filha, inclusive as que outras pessoas escreverem daqui a um ano. Se o Produto tem uma regra ("preço nunca negativo"), ela só é garantida enquanto o atributo for private. A recomendação comum: atributos private; métodos protected quando forem ferramentas só para as filhas.


4. A ordem de construção e destruição

Se o livro carrega um produto por dentro, quem nasce primeiro? Vamos perguntar ao programa:

#include <iostream>
#include <string>

class Produto {
public:
    Produto(std::string nome) : nome_{nome} {
        std::cout << "  Produto(" << nome_ << ")\n";
    }
    ~Produto() {
        std::cout << "  ~Produto(" << nome_ << ")\n";
    }
private:
    std::string nome_;
};

class Livro : public Produto {
public:
    Livro(std::string nome, std::string autor) : Produto{nome}, autor_{autor} {
        std::cout << "  Livro(" << autor_ << ")\n";
    }
    ~Livro() {
        std::cout << "  ~Livro(" << autor_ << ")\n";
    }
private:
    std::string autor_;
};

int main() {
    std::cout << "Criando o livro:\n";
    Livro livro{"Dom Casmurro", "Machado de Assis"};
    std::cout << "Fim do main:\n";
}
$ g++ -std=c++23 -Wall -Wextra ordem.cpp -o ordem
$ ./ordem
Criando o livro:
  Produto(Dom Casmurro)        « primeiro a base (o alicerce)...
  Livro(Machado de Assis)      « ...depois a filha
Fim do main:
  ~Livro(Machado de Assis)     « na destruição, ao contrário: primeiro a filha...
  ~Produto(Dom Casmurro)       « ...depois a base

Pense numa casa: primeiro o alicerce, depois as paredes; na demolição, primeiro as paredes, depois o alicerce. A base nasce primeiro porque a filha pode precisar dela já no próprio construtor; e morre por último porque o destrutor da filha ainda pode usá-la. É a mesma regra de "ordem inversa" da aula Destrutores e RAII.


5. Redefinir um método da base

O mostrar() herdado não sabe do autor. A filha pode escrever um método com o mesmo nome; para o Livro, ele passa a valer no lugar do da base. E, dentro dele, Produto::mostrar() chama a versão original, para não repetir código:

class Livro : public Produto {
public:
    Livro(std::string nome, double preco, std::string autor)
        : Produto{nome, preco}, autor_{autor} {}

    void mostrar() const {
        Produto::mostrar();                    // a versão da classe base
        std::cout << "  autor: " << autor_ << '\n';
    }
private:
    std::string autor_;
};

void imprimir_etiqueta(const Produto& p) {
    p.mostrar();
}

int main() {
    Livro livro{"Dom Casmurro", 40, "Machado de Assis"};
    livro.mostrar();
    std::cout << "Pela função imprimir_etiqueta:\n";
    imprimir_etiqueta(livro);
}
$ g++ -std=c++23 -Wall -Wextra redefinir.cpp -o redefinir
$ ./redefinir
Dom Casmurro — R$ 40
  autor: Machado de Assis
Pela função imprimir_etiqueta:
Dom Casmurro — R$ 40           « o autor sumiu!

Duas coisas aconteceram:

  • Um Livro pode ser passado onde se espera um Produto. A função imprimir_etiqueta recebe const Produto&, e aceitou o livro sem reclamar. Faz sentido: todo livro é um produto.
  • Mas, lá dentro, rodou o mostrar() do Produto, e o autor não apareceu. Para a função, p é "um produto qualquer", e o C++ escolheu o método olhando o tipo da referência, não o objeto de verdade.

Fazer o C++ olhar o objeto de verdade é o assunto da próxima aula, Polimorfismo (virtual), e é onde a herança mostra toda a sua força.


6. Herança pública × privada (só a ideia)

A palavra depois dos dois-pontos decide como o que é público na base aparece na filha:

FormaO public da base vira, na filha…Significado
class Livro : public Produtopublic"Livro é um Produto". Todo mundo pode tratar o livro como produto.
class Livro : protected ProdutoprotectedRaríssimo.
class Livro : private Produtoprivate"Livro usa um Produto por dentro", mas não quer que ninguém saiba.

A herança privada é rara: quase sempre um atributo (composição) faz o mesmo de forma mais clara. Ela importa porque é o padrão de class: se você esquecer a palavra public, a herança vira privada sem aviso, e os métodos herdados ficam inacessíveis no main (cenário em Quando dá errado). Com struct, o padrão é public.

E herdar de duas classes?

O C++ permite herança múltipla: class Ebook : public Livro, public Arquivo. É poderosa e cheia de detalhes (o que acontece se as duas bases tiverem um método com o mesmo nome?). O uso mais comum e seguro é herdar de uma classe e de várias "interfaces", que você vai conhecer em Classes Abstratas.


7. final: ninguém herda daqui C++11

Às vezes uma classe não foi pensada para ser base de outras. Escreva final depois do nome, e o compilador recusa qualquer tentativa de herdar dela:

class Livro final : public Produto {
    // ...
};

class Ebook : public Livro {        // tentativa de herdar de Livro
    // ...
};
$ g++ -std=c++23 -Wall -Wextra final.cpp -o final
final.cpp:20:7: error: cannot derive from ‘final’ base ‘Livro’ in derived type ‘Ebook’
   20 | class Ebook : public Livro {
      |       ^~~~~

"Não é possível derivar da base final Livro no tipo derivado Ebook". O Clang do editor diz base 'Livro' is marked 'final'. O final também vale para um método só, como você vai ver em Polimorfismo.


Quando dá errado

"no matching function for call to ‘Produto::Produto()’"
$ g++ -std=c++23 -Wall -Wextra h3.cpp -o h3
h3.cpp: In constructor ‘Livro::Livro(std::string, double, std::string)’:
h3.cpp:15:23: error: no matching function for call to ‘Produto::Produto()’
   15 |         : autor_{autor} {}
      |                       ^
h3.cpp:15:23: note: there are 3 candidates
h3.cpp:6:5: note: candidate 1: ‘Produto::Produto(std::string, double)’
    6 |     Produto(std::string nome, double preco) : nome_{nome}, preco_{preco} {}
      |     ^~~~~~~
h3.cpp:6:5: note: candidate expects 2 arguments, 0 provided
h3.cpp:4:7: note: candidate 2: ‘constexpr Produto::Produto(const Produto&)’
    4 | class Produto {
      |       ^~~~~~~
h3.cpp:4:7: note: candidate expects 1 argument, 0 provided
h3.cpp:4:7: note: candidate 3: ‘constexpr Produto::Produto(Produto&&)’
h3.cpp:4:7: note: candidate expects 1 argument, 0 provided
h3.cpp:14:23: warning: unused parameter ‘nome’ [-Wunused-parameter]
   14 |     Livro(std::string nome, double preco, std::string autor)
      |           ~~~~~~~~~~~~^~~~
h3.cpp:14:36: warning: unused parameter ‘preco’ [-Wunused-parameter]
   14 |     Livro(std::string nome, double preco, std::string autor)
      |                             ~~~~~~~^~~~~

O que significa: o construtor do Livro não disse como construir a parte Produto, então o C++ procurou um Produto() sem argumentos. Os três "candidatos" são os construtores que existem: o seu (2 argumentos), o de cópia e o de movimento (1 argumento cada). Nenhum aceita 0. Os avisos de parâmetros não usados são a pista: nome e preco chegaram e ninguém os usou. O Clang resume: constructor for 'Livro' must explicitly initialize the base class 'Produto' which does not have a default constructor.

Como consertar: acrescente Produto{nome, preco} no começo da lista de inicialização.

"is private within this context" dentro da classe filha

O que significa: a filha tentou usar um atributo private da base (o h1.cpp da seção 3). O Clang diz 'preco_' is a private member of 'Produto'.

Como consertar: use um método público da base (preco()), ou, se fizer sentido, troque o atributo para protected.

Esqueci o public: "is inaccessible within this context"
class Livro : Produto {     // faltou o public
    // ...
};

int main() {
    Livro livro{"Dom Casmurro", 40, "Machado de Assis"};
    livro.mostrar();
}
$ g++ -std=c++23 -Wall -Wextra h4.cpp -o h4
h4.cpp: In function ‘int main()’:
h4.cpp:23:18: error: ‘void Produto::mostrar() const’ is inaccessible within this context
   23 |     livro.mostrar();
      |     ~~~~~~~~~~~~~^~
h4.cpp:7:10: note: declared here
    7 |     void mostrar() const { std::cout << nome_ << " — R$ " << preco_ << '\n'; }
      |          ^~~~~~~
h4.cpp:23:18: error: ‘Produto’ is not an accessible base of ‘Livro’
   23 |     livro.mostrar();
      |     ~~~~~~~~~~~~~^~

O que significa: "mostrar é inacessível aqui" e "Produto não é uma base acessível de Livro". Sem a palavra public, uma class herda de forma privada: o mostrar(), que era público, virou privado dentro do Livro. (Clang: 'mostrar' is a private member of 'Produto'.)

Como consertar: class Livro : public Produto.

"cannot derive from ‘final’ base"

O que significa: a classe de que você tentou herdar foi marcada com final (seção 7).

Como consertar: se a classe é sua e você decidiu que ela pode ter filhas, tire o final. Se não é sua (uma biblioteca), use composição: guarde um objeto dela como atributo.

Redefini o método, mas a versão da base continua rodando

O que significa: você chamou o método por uma referência ou ponteiro do tipo da base (const Produto&, Produto*), como no imprimir_etiqueta da seção 5. Sem virtual, o C++ escolhe o método pelo tipo da referência.

Como consertar: marque o método da base como virtual. A explicação completa está em Polimorfismo.


Missão

Missão: crie a classe Revista, filha de Produto, com um atributo int edicao_. O construtor recebe nome, preço e edição e chama o construtor do Produto. Redefina mostrar() para chamar o Produto::mostrar() e depois mostrar Edição 42. Por fim, tire as barras // das duas linhas do main.

#include <iostream>
#include <string>

class Produto {
public:
    Produto(std::string nome, double preco) : nome_{nome}, preco_{preco} {}
    void mostrar() const {
        std::cout << nome_ << " — R$ " << preco_ << '\n';
    }
private:
    std::string nome_;
    double preco_;
};

// escreva a classe Revista aqui

int main() {
    Produto caneta{"Caneta azul", 3.5};
    caneta.mostrar();
    // Revista revista{"Superinteressante", 25, 42};
    // revista.mostrar();
}
#include <iostream>
#include <string>

class Produto {
public:
    Produto(std::string nome, double preco) : nome_{nome}, preco_{preco} {}
    void mostrar() const {
        std::cout << nome_ << " — R$ " << preco_ << '\n';
    }
private:
    std::string nome_;
    double preco_;
};

class Revista : public Produto {
public:
    Revista(std::string nome, double preco, int edicao)
        : Produto{nome, preco}, edicao_{edicao} {}

    void mostrar() const {
        Produto::mostrar();
        std::cout << "  Edição " << edicao_ << '\n';
    }
private:
    int edicao_;
};

int main() {
    Produto caneta{"Caneta azul", 3.5};
    caneta.mostrar();
    Revista revista{"Superinteressante", 25, 42};
    revista.mostrar();
}

A saída da solução no laboratório:

$ ./revista
Caneta azul — R$ 3.5
Superinteressante — R$ 25
  Edição 42

Referência

CódigoO que faz
class Filha : public Base { … };Herança pública: "Filha é uma Base"
Filha(…) : Base{…}, atributo_{…} {}Constrói a parte da base, depois os atributos da filha
using Base::Base;Herda os construtores da base
protected:Visível para a classe e as filhas; invisível para o resto
Base::metodo();Chama a versão da base de um método redefinido
class X final : public BaseProíbe que alguém herde de X
class Filha : BaseHerança privada (padrão de class)
OrdemConstrói base → filha; destrói filha → base

Exercícios

1. A declaração

Complete para declarar Cachorro como filha pública de Animal:

class Cachorro { … };

2. Chamar o construtor da base

O construtor de Animal recebe o nome. Complete a lista de inicialização do Cachorro:

Cachorro(std::string nome, std::string raca) : , raca_{raca} {}

3. O nível certo

Qual palavra deixa um membro visível para as classes filhas, mas não para o main?

4. Ao criar um Livro (filho de Produto) e deixá-lo sair do escopo, em que ordem rodam construtores e destrutores?

5. Qual destes pares não passa no teste do "é um"?

6. Você escreveu class Livro : Produto (sem public) e livro.mostrar() no main deu erro. Por quê?


Resumo

  • class Filha : public Base cria uma classe que recebe tudo da base e pode acrescentar atributos e métodos.
  • Use herança quando a frase "Filha é uma Base" faz sentido; quando é "tem um", use um atributo.
  • O construtor da filha chama o da base na lista de inicialização: : Base{…}, atributo_{…}.
  • private da base é proibido até para a filha; protected libera só para as filhas.
  • A base nasce primeiro e morre por último.
  • Um método com o mesmo nome na filha substitui o da base; Base::metodo() chama o original. Por uma referência da base, sem virtual, roda o da base.
  • Sem public, a herança de uma class é privada. final proíbe herdar.