Polimorfismo em Tempo de Execução com Classes Abstratas em C++

Tarefa 1: Implementando Polimorfismo com Classes Abstratas

Este exercício demonstra o uso de polimorfismo em tempo de execução em C++ utilizando classes abstratas e funções virtuais puras. Definimos uma classe base abstrata Publisher com interfaces virtuais puras publish() e use(). Classes derivadas como Book, Film e Music implementam essas interfaces para fornecer comportamentos específicos.

Código-Fonte

publisher.hpp


#pragma once

#include <string>

// Classe base abstrata para publicações
class Publisher {
public:
    Publisher(const std::string &name_ = "");
    virtual ~Publisher() = default;

    // Interfaces virtuais puras
    virtual void publish() const = 0;
    virtual void use() const = 0;

protected:
    std::string name; // Nome da publicação
};

// Classe derivada para livros
class Book: public Publisher {
public:
    Book(const std::string &name_ = "", const std::string &author_ = "");

    void publish() const override;
    void use() const override;

private:
    std::string author; // Autor do livro
};

// Classe derivada para filmes
class Film: public Publisher {
public:
    Film(const std::string &name_ = "", const std::string &director_ = "");

    void publish() const override;
    void use() const override;

private:
    std::string director; // Diretor do filme
};

// Classe derivada para músicas
class Music: public Publisher {
public:
    Music(const std::string &name_ = "", const std::string &artist_ = "");

    void publish() const override;
    void use() const override;

private:
    std::string artist; // Artista da música
};

publisher.cpp


#include <iostream>
#include <string>
#include "publisher.hpp"

// Implementação do construtor de Publisher
Publisher::Publisher(const std::string &name_): name {name_} {
}

// Implementação de Book
Book::Book(const std::string &name_ , const std::string &author_ ): Publisher{name_}, author{author_} {
}

void Book::publish() const {
    std::cout << "Publicando livro: \"" << name << "\" por " << author << '\n';
}

void Book::use() const {
    std::cout << "Lendo o livro: \"" << name << "\" por " << author << '\n';
}

// Implementação de Film
Film::Film(const std::string &name_, const std::string &director_):Publisher{name_},director{director_} {
}

void Film::publish() const {
    std::cout << "Publicando filme: \"" << name << "\" dirigido por " << director << '\n';
}

void Film::use() const {
    std::cout << "Assistindo ao filme: \"" << name << "\" dirigido por " << director << '\n';
}

// Implementação de Music
Music::Music(const std::string &name_, const std::string &artist_): Publisher{name_}, artist{artist_} {
}

void Music::publish() const {
    std::cout << "Publicando música: \"" << name << "\" por " << artist << '\n';
}

void Music::use() const {
    std::cout << "Ouvindo a música: \"" << name << "\" por " << artist << '\n';
}

task1.cpp


#include <memory>
#include <iostream>
#include <vector>
#include "publisher.hpp"

// Teste com ponteiros brutos
void testRawPointers() {
   std::vector<Publisher *> publications;

   publications.push_back(new Book("O Senhor dos Anéis", "J.R.R. Tolkien"));
   publications.push_back(new Film("O Poderoso Chefão", "Francis Ford Coppola"));
   publications.push_back(new Music("Bohemian Rhapsody", "Queen"));

   for(Publisher *pub_ptr : publications) {
        pub_ptr->publish();
        pub_ptr->use();
        std::cout << '\n';
        delete pub_ptr; // Liberação manual de memória
   }
}

// Teste com ponteiros inteligentes (unique_ptr)
void testSmartPointers() {
    std::vector<std::unique_ptr<Publisher>> publications;

    publications.push_back(std::make_unique<Book>("1984", "George Orwell"));
    publications.push_back(std::make_unique<Film>("Pulp Fiction", "Quentin Tarantino"));
    publications.push_back(std::make_unique<Music>("Stairway to Heaven", "Led Zeppelin"));

    for(const auto &pub_ptr : publications) {
        pub_ptr->publish();
        pub_ptr->use();
        std::cout << '\n';
        // A memória é liberada automaticamente pelo unique_ptr
    }
}

// Teste com objetos locais
void testDirectUsage() {
    Book book("O Guia do Mochileiro das Galáxias", "Douglas Adams");
    book.publish();
    book.use();
}

int main() {
    std::cout << "--- Testando Polimorfismo em Tempo de Execução ---\n";

    std::cout << "\n--- Teste 1: Usando Ponteiros Brutos ---\n";
    testRawPointers();

    std::cout << "\n--- Teste 2: Usando Ponteiros Inteligentes (unique_ptr) ---\n";
    testSmartPointers();

    std::cout << "\n--- Teste 3: Uso Direto das Classes Derivadas ---\n";
    testDirectUsage();

    return 0;
}

Respostas às Questões

Questão 1

  • (1) A classe Publisher possui duas funções virtuais puras: publish() e use(). Isso é evidenciado pela sintaxe virtual ... = 0;.
  • (2) Não é possível instanciar um objeto da classe Publisher diretamente. Por ser uma classe abstrata devido às suas funções virtuais puras, o compilador impedirá a criação de objetos Publisher, resultando em um erro como "não é possível instanciar um objeto de tipo abstrato 'Publisher'".

Questão 2

  • (1) Para que as classes derivadas compilem corretamente, elas devem implementar as funções virtuais puras publish() e use(). A declaração deve usar a palavra-chave override para garantir que a função está, de fato, sobrescrevendo uma função virtual da classe base. Exemplo para Film: void publish() const override;
  • (2) Se uma função virtual pura da classe base não for implementada em uma classe derivada, o compilador emitirá um erro indicando a incompatibilidade ou a ausência da implementação necessária. Por exemplo, um erro como "a declaração é incompatível com 'void Film::publish() const'".

Questão 3

  • (1) No loop for(Publisher *ptr : v), a variável ptr é declarada como um ponteiro do tipo Publisher.
  • (2) O ponteiro ptr, embora declarado como Publisher*, aponta na verdade para objetos das classes derivadas: Book, Film e Music, dependendo do objeto que foi adicionado ao vetor.
  • (3) A palavra-chave virtual é essencial para o destruidor (virtual ~Publisher() = default;). Quando um objeto derivado é deletado através de um ponteiro da classe base (delete ptr;), o destruidor virtual garante que o destruidor da classe derivada apropriada seja chamado antes do destruidor da classe base. Sem o virtual, apenas o destruidor da classe base seria chamado, o que poderia levar a vazamentos de memória e comportamento indefinido se os destruidores das classes derivadas tivessem lógica específica (como desalocação de recursos).

Tarefa 2: Gerenciamento de Dados de Vendas de Livros

Estee exercício foca na criação de classes para representar informações de livros e seus registros de vendas. Implementamos a sobrecarga do operador de inserção (<<) para facilitar a exibição formatada de objetos Book e BookSale, e utilizamos algoritmos de ordenação para processar os dados de vendas.

Código-Fonte

book.hpp


#pragma once
#include <string>
#include <ostream>

// Classe para representar informações de um livro
class Book {
public:
    Book(const std::string &name_,
         const std::string &author_,
         const std::string &translator_,
         const std::string &isbn_,
         double price_);

    // Sobrecarga do operador de inserção para exibição
    friend std::ostream& operator<<(std::ostream &out, const Book &book);

private:
    std::string name;        // Título do livro
    std::string author;      // Autor
    std::string translator;  // Tradutor
    std::string isbn;        // Código ISBN
    double price;        // Preço de capa
};

book.cpp


#include <iomanip>
#include <iostream>
#include <string>
#include "book.hpp"

// Implementação do construtor de Book
Book::Book(const std::string &name_,
          const std::string &author_,
          const std::string &translator_,
          const std::string &isbn_,
          double price_):name{name_}, author{author_}, translator{translator_}, isbn{isbn_}, price{price_} {
}

// Implementação da sobrecarga do operador << para Book
std::ostream& operator<<(std::ostream &out, const Book &book) {
    using std::left;
    using std::setw;

    out << left; // Alinhamento à esquerda
    out << setw(15) << "Título:" << book.name << '\n'
        << setw(15) << "Autor:" << book.author << '\n'
        << setw(15) << "Tradutor:" << book.translator << '\n'
        << setw(15) << "ISBN:" << book.isbn << '\n'
        << setw(15) << "Preço:" << book.price;

    return out;
}

booksale.hpp


#pragma once

#include <string>
#include "book.hpp"

// Classe para registrar vendas de livros
class BookSale {
public:
    BookSale(const Book &book_ref_, double sale_price_, int quantity_);
    int get_quantity() const;   // Retorna a quantidade vendida
    double get_revenue() const;   // Retorna a receita total
    
    // Sobrecarga do operador de inserção para exibição
    friend std::ostream& operator<<(std::ostream &out, const BookSale &item);

private:
    Book book_details;      // Detalhes do livro vendido
    double sale_price;      // Preço unitário de venda
    int quantity_sold;       // Quantidade vendida
};

booksale.cpp


#include <iomanip>
#include <iostream>
#include <string>
#include "booksale.hpp"

// Implementação do construtor de BookSale
BookSale::BookSale(const Book &book_ref_,
                   double sales_price_,
                   int sales_amount_): book_details{book_ref_}, sale_price{sales_price_}, quantity_sold{sales_amount_} {
}

// Retorna a quantidade vendida
int BookSale::get_quantity() const {
    return quantity_sold;
}

// Calcula e retorna a receita total
double BookSale::get_revenue() const {
    return quantity_sold * sale_price;
}

// Implementação da sobrecarga do operador << para BookSale
std::ostream& operator<<(std::ostream &out, const BookSale &item) {
    using std::left;
    using std::setw;

    out << left;
    // Exibe os detalhes do livro usando o operador << sobrecarregado para Book
    out << item.book_details << '\n'
        << setw(15) << "Preço Unitário:" << item.sale_price << '\n'
        << setw(15) << "Quantidade Vendida:" << item.quantity_sold << '\n'
        << setw(15) << "Receita Total:" << item.get_revenue();

    return out;
}

task2.cpp


#include <algorithm>
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>
#include "booksale.hpp"

// Função de comparação para ordenar por quantidade vendida (decrescente)
bool compareSalesByQuantity(const BookSale &sale1, const BookSale &sale2) {
    return sale1.get_quantity() > sale2.get_quantity();
}

void manageSales() {
    using std::cin;
    using std::cout;
    using std::getline;
    using std::sort;
    using std::string;
    using std::vector;
    using std::ws; // Para consumir espaços em branco

    vector<BookSale> sales_ledger; // Registro de vendas

    int num_books;
    cout << "Digite a quantidade de registros de vendas a inserir: ";
    cin >> num_books;

    cout << "\n--- Inserindo Registros de Vendas ---\n";
    for(int i = 0; i < num_books; ++i) {
        string title, author, translator, isbn_code;
        double cover_price;
        cout << string(10, '*') << " Registro #" << (i + 1) << " " << string(10, '*') << '\n';
        cout << "Título: "; getline(cin >> ws, title);
        cout << "Autor: "; getline(cin >> ws, author);
        cout << "Tradutor: "; getline(cin >> ws, translator);
        cout << "ISBN: "; getline(cin >> ws, isbn_code);
        cout << "Preço de Capa: "; cin >> cover_price;

        Book current_book(title, author, translator, isbn_code, cover_price);

        double selling_price;
        int units_sold;

        cout << "Preço de Venda Unitário: "; cin >> selling_price;
        cout << "Unidades Vendidas: "; cin >> units_sold;

        BookSale sale_record(current_book, selling_price, units_sold);
        sales_ledger.push_back(sale_record);
        cout << '\n';
    }

    // Ordena os registros de vendas pela quantidade vendida (decrescente)
    sort(sales_ledger.begin(), sales_ledger.end(), compareSalesByQuantity);

    // Exibe o relatório de vendas ordenado
    cout << string(20, '=') << " Relatório de Vendas de Livros " << string(20, '=') << '\n';
    for(const auto &record : sales_ledger) {
        cout << record << "\n" << string(45, '-') << '\n';
    }
}

int main() {
    manageSales();
    return 0;
}

Respostas às Questões

Questão 1

  • (1) O operador de inserção << foi sobrecarregado duas vezes: uma para a classe Book e outra para a classe BookSale.

  • (2) O código para a sobrecarga do operador << para Book é: ```

    std::ostream& operator<<(std::ostream &out, const Book &book) { using std::left; using std::setw; out << left; out << setw(15) << "Título:" << book.name << '\n' << setw(15) << "Autor:" << book.author << '\n' << setw(15) << "Tradutor:" << book.translator << '\n' << setw(15) << "ISBN:" << book.isbn << '\n' << setw(15) << "Preço:" << book.price; return out; }

    
     E para `BookSale`: ```
    
    std::ostream& operator<<(std::ostream &out, const BookSale &item) {
        using std::left;
        using std::setw;
        out << left;
        out << item.book_details << '\n'
            << setw(15) << "Preço Unitário:" << item.sale_price << '\n'
            << setw(15) << "Quantidade Vendida:" << item.quantity_sold << '\n'
            << setw(15) << "Receita Total:" << item.get_revenue();
        return out;
    }
    
    

Questão 2

  • (1) Os dados de vendas são armazenados em um std::vector<BookSale> chamado sales_ledger. A função std::sort é chamada para ordenar este vetor do início (sales_ledger.begin()) ao fim (sales_ledger.end()), utilizando a função de comparação compareSalesByQuantity para definir a ordem (decrescente pela quantidade vendida).

  • (2) A ordenação pode ser feita utilizando uma expressão lambda diretamente na chamada de std::sort: ```

      // Ordena os registros de vendas pela quantidade vendida (decrescente) usando lambda
      std::sort(sales_ledger.begin(), sales_ledger.end(),
          [](const BookSale& saleA, const BookSale& saleB) {
              return saleA.get_quantity() > saleB.get_quantity();
          }
      );
    
    
    

Tarefa 4: Simulação de Interações de Animais de Estimação Mecânicos

Este exercício explora o polimorfismo com classes derivadas de uma classe base abstrata MachinePet. Definimos PetCat e PetDog que herdam de MachinePet e implementam a função virtual pura talk() para produzir sons distintos.

Código-Fonte

pet.hpp


#pragma once
#include <string>

// Classe base abstrata para animais de estimação mecânicos
class MachinePet
{
public:
	MachinePet(const std::string &name0) : nick_name{name0} {}
	std::string get_nickname() const { return nick_name; }
	// Função virtual pura para o som do animal
	virtual std::string talk() const = 0;
private:
	std::string nick_name; // Nome do animal
};

// Classe derivada para um gato mecânico
class PetCat : public MachinePet
{
public:
	PetCat(const std::string& name0 = "GatoPadrão") : MachinePet{name0} { }
	// Implementação do som do gato
	std::string talk() const override { return "miau"; }
};

// Classe derivada para um cachorro mecânico
class PetDog : public MachinePet
{
public:
	PetDog(const std::string& name0 = "CachorroPadrão") : MachinePet{name0} { }
	// Implementação do som do cachorro
	std::string talk() const override { return "woof"; }
};

task4.cpp


#include <iostream>
#include <memory>
#include <vector>
#include "pet.hpp"

// Teste com ponteiros brutos para animais
void testRawPointersPets() {
    std::vector<MachinePet *> zoo;

    zoo.push_back(new PetCat("Miku"));
    zoo.push_back(new PetDog("Rex"));

    for(MachinePet *pet_ptr : zoo) {
        std::cout << pet_ptr->get_nickname() << " diz: " << pet_ptr->talk() << '\n';
        delete pet_ptr; // Necessário liberar memória manualmente
    }
}

// Teste com ponteiros inteligentes (unique_ptr) para animais
void testSmartPointersPets() {
    std::vector<std::unique_ptr<MachinePet>> zoo;

    zoo.push_back(std::make_unique<PetCat>("Luna"));
    zoo.push_back(std::make_unique<PetDog>("Buddy"));

    for(const auto &pet_ptr : zoo)
        std::cout << pet_ptr->get_nickname() << " diz: " << pet_ptr->talk() << '\n';
        // Memória liberada automaticamente
}

// Teste com uso direto das classes derivadas
void testDirectUsagePets() {
    // MachinePet pet("Criatura"); // Erro de compilação: não pode instanciar classe abstrata

    const PetCat cat("Mia");
    std::cout << cat.get_nickname() << " diz: " << cat.talk() << '\n';

    const PetDog dog("Thor");
    std::cout << dog.get_nickname() << " diz: " << dog.talk() << '\n';
}

int main() {
    std::cout << "--- Testando Animais Mecânicos ---\n";

    std::cout << "\n--- Teste 1: Ponteiros Brutos ---\n";
    testRawPointersPets();

    std::cout << "\n--- Teste 2: Ponteiros Inteligentes ---\n";
    testSmartPointersPets();

    std::cout << "\n--- Teste 3: Uso Direto ---\n";
    testDirectUsagePets();

    return 0;
}

Tarefa 5: Implementação de Números Complexos com Templates

Este exercício desenvolve uma classe template Complex para representar números complexos. A clasce suporta operações como adição, comparação e entrada/saída de dados, utilizando templates para permitir que os componentes real e imaginário sejam de diferentes tipos numéricos (como int ou double).

Código-Fonte

Complex.hpp


#pragma once
#include <iostream>

template <typename T>
class Complex
{
public:
	// Construtor padrão e com inicialização
	Complex() = default;
	Complex(T real_part, T imag_part) : real{real_part}, imag{imag_part} { }

	// Sobrecarga do operador de atribuição de adição
	void operator+=(const Complex& c) {
		real += c.real;
		imag += c.imag;
	}

	// Getters para as partes real e imaginária
	T get_real() const { return real; }
	T get_imag() const { return imag; }

	// Declaração de operadores friend como templates
	template <typename U> friend std::ostream& operator<<(std::ostream& out, const Complex& c);
	template <typename U> friend std::istream& operator>>(std::istream& in, Complex& c);
	template <typename U> friend Complex operator+(const Complex& c1, const Complex& c2);
	template <typename U> friend bool operator==(const Complex& c1, const Complex& c2);

private:
	T real; // Parte real
	T imag; // Parte imaginária
};

// Implementação da sobrecarga do operador << para Complex
template <typename T>
std::ostream& operator<<(std::ostream& out, const Complex& c)
{
	out << c.real << " + i*" << c.imag; // Formato: real + i*imag
	return out;
}

// Implementação da sobrecarga do operador >> para Complex
template <typename T>
std::istream& operator>>(std::istream& in, Complex& c)
{
	in >> c.real >> c.imag; // Lê real e imaginário
	return in;
}

// Implementação do operador + para Complex
template <typename T>
Complex operator+(const Complex& c1, const Complex& c2)
{
	// Retorna um novo Complexo com a soma das partes
	return Complex(c1.real + c2.real, c1.imag + c2.imag);
}

// Implementação do operador == para Complex
template <typename T>
bool operator==(const Complex& c1, const Complex& c2)
{
	// Compara se ambas as partes são iguais
	return(c1.real == c2.real && c1.imag == c2.imag);
}

task5.cpp


#include <iostream>
#include "Complex.hpp" // Inclui a definição da classe template Complex

// Teste 1: Operações básicas com Complex<int>
void testComplexInt() {
    using std::cout;
    using std::boolalpha;

    Complex<int> c1(2, -5);   // Cria um número complexo 2 - 5i
    Complex<int> c2(c1);       // Cria uma cópia de c1

    cout << "c1 = " << c1 << '\n';
    cout << "c2 = " << c2 << '\n';

    // Testando o operador +
    Complex<int> sum = c1 + c2;
    cout << "c1 + c2 = " << sum << '\n';

    // Testando o operador +=
    c1 += c2;
    cout << "Após c1 += c2, c1 = " << c1 << '\n';

    // Testando o operador ==
    cout << "c1 == c2? " << boolalpha << (c1 == c2) << '\n';
}

// Teste 2: Entrada/Saída e acesso a partes com Complex<double>
void testComplexDouble() {
    using std::cin;
    using std::cout;

    Complex<double> num1, num2; // Cria dois números complexos com partes double
    cout << "Digite os componentes real e imaginário para c1 (separados por espaço): ";
    cin >> num1; // Usa o operador >> sobrecarregado
    cout << "Digite os componentes real e imaginário para c2 (separados por espaço): ";
    cin >> num2; // Usa o operador >> sobrecarregado

    cout << "c1 inserido = " << num1 << '\n'; // Usa o operador << sobrecarregado
    cout << "c2 inserido = " << num2 << '\n'; // Usa o operador << sobrecarregado

    // Testando os métodos get_real() e get_imag()
    const Complex<double> c3(num1); // Cria uma constante a partir de num1
    cout << "Parte real de c3 = " << c3.get_real() << '\n';
    cout << "Parte imaginária de c3 = " << c3.get_imag() << '\n';
}

int main() {
    std::cout << "--- Teste 1: Operações com Complex<int> ---\n";
    testComplexInt();

    std::cout << "\n--- Teste 2: Entrada/Saída com Complex<double> ---\n";
    testComplexDouble();

    return 0;
}
</double></int>

Tags: C++ Programação Orientada a Objetos Polimorfismo Classes Abstratas Virtual

Publicado em 6-18 19:55