Desafio C++
Instrumentos musicais com final
Enunciado
Crie uma hierarquia de instrumentos musicais. A classe base abstrata Instrumento deve ter um método virtual puro tocar() const e um destrutor virtual. As classes Corda e Sopro herdam de Instrumento e implementam tocar(). A classe Violao herda de Corda e sobrescreve tocar(). A classe Flauta herda de Sopro e é marcada como final, impedindo herança adicional. No main, crie um vetor de std::unique_ptr<Instrumento>, adicione um Violao e uma Flauta, e chame tocar() para cada um. Além disso, tente (comentado) criar uma classe que herde de Flauta para ver o erro de compilação.
Requisitos
Instrumentoé classe abstrata comvirtual void tocar() const = 0e destrutor virtual.CordaeSoproherdam publicamente deInstrumentoe implementamtocar()comoverride.Violaoherda deCordae sobrescrevetocar()comoverride.Flautaherda deSoproe é marcada comofinal.- O
mainusastd::unique_ptr<Instrumento>e chamatocar()polimorficamente. - Inclua um comentário mostrando a tentativa de herdar de
Flautae explique o erro.
Código inicial
#include <iostream>
#include <memory>
#include <vector>
// TODO: definir classes Instrumento, Corda, Sopro, Violao, Flauta
int main() {
// TODO: criar vetor de unique_ptr<Instrumento> e chamar tocar()
return 0;
}
Saída esperada
Violao: acorde harmonioso
Flauta: som suave
Ver dica
Use = 0 para tornar tocar() virtual pura. A palavra-chave final vai após o nome da classe: class Flauta final : public Sopro.
Mostrar solução
#include <iostream>
#include <memory>
#include <vector>
class Instrumento {
public:
virtual void tocar() const = 0; // método virtual puro
virtual ~Instrumento() = default; // destrutor virtual
};
class Corda : public Instrumento {
public:
void tocar() const override {
std::cout << "Corda: som dedilhado\n";
}
};
class Sopro : public Instrumento {
public:
void tocar() const override {
std::cout << "Sopro: som soprado\n";
}
};
class Violao : public Corda {
public:
void tocar() const override {
std::cout << "Violao: acorde harmonioso\n";
}
};
class Flauta final : public Sopro {
public:
void tocar() const override {
std::cout << "Flauta: som suave\n";
}
};
// Tentativa de herdar de Flauta (descomente para ver o erro):
// class FlautaDoce : public Flauta { };
// Erro: cannot derive from 'final' base 'Flauta'
int main() {
std::vector<std::unique_ptr<Instrumento>> instrumentos;
instrumentos.push_back(std::make_unique<Violao>());
instrumentos.push_back(std::make_unique<Flauta>());
for (const auto& i : instrumentos) {
i->tocar();
}
return 0;
}
Passo a passo
- A classe
Instrumentoé abstrata porque contém o método virtual purovirtual void tocar() const = 0;. Isso significa que não podemos instanciarInstrumentodiretamente. - Incluímos um destrutor virtual
virtual ~Instrumento() = default;para garantir que, ao deletar um objeto via ponteiro paraInstrumento, o destrutor correto da classe derivada seja chamado. - A classe
Cordaherda publicamente deInstrumentoe implementatocar()comoverride, imprimindo uma mensagem genérica de corda. - A classe
Soprotambém herda publicamente e implementatocar()comoverride, imprimindo uma mensagem genérica de sopro. - A classe
Violaoherda deCordae sobrescrevetocar()comoverride, fornecendo uma implementação específica. - A classe
Flautaherda deSoproe é marcada comofinal. Isso impede que outras classes herdem deFlauta. Seu métodotocar()é sobrescrito. - No
main, criamos umstd::vector<std::unique_ptr<Instrumento>>e adicionamos umViolaoe umaFlautausandostd::make_unique. - Iteramos sobre o vetor e chamamos
tocar(). Devido ao polimorfismo, as implementações corretas são chamadas. - O código comentado mostra a tentativa de herdar de
Flauta. Se descomentado, o compilador emitirá um erro porqueFlautaéfinal.
Por que funciona
A classe abstrata Instrumento define a interface que todas as classes derivadas devem seguir. O método virtual puro força as subclasses a implementarem tocar(). O polimorfismo permite tratar objetos de diferentes classes derivadas através de ponteiros para a base. O destrutor virtual garante a destruição correta. A palavra-chave final impede herança adicional, o que pode ser útil para evitar modificações indesejadas ou para permitir otimizações.
Erros comuns
- Esquecer
= 0no método virtual puro: Setocar()não for puro,Instrumentonão será abstrata e poderá ser instanciada, mas não é o desejado. Usevirtual void tocar() const = 0;. - Não usar
override: Se a assinatura não coincidir (por exemplo, faltarconst), o método não sobrescreve e o compilador pode não avisar. Useoverridepara que o compilador verifique. - Destrutor não virtual: Se
~Instrumento()não for virtual, ao deletar umViolaoviaunique_ptr<Instrumento>, apenas o destrutor deInstrumentoseria chamado, causando vazamento de recursos. Declarevirtual ~Instrumento() = default;. - Tentar herdar de classe
final: Se você tentarclass FlautaDoce : public Flauta { }, o compilador emitirá um erro. Removafinalse precisar herdar.
Outra forma de resolver
Poderia-se usar composição em vez de herança, mas a herança é adequada para modelar a relação "é um" entre instrumentos. Outra alternativa é usar std::variant ou std::function para armazenar callables, mas isso foge do conceito de herança e polimorfismo.
Saída esperada
Violao: acorde harmonioso
Flauta: som suave