Menu
Coddy logo textTech

Funções Virtuais Puras

Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy — lição 59 de 104.

Uma função virtual pura é uma função virtual que não possui implementação na classe base. Ela é declarada atribuindo = 0 à declaração da função. Isso informa ao compilador que as classes derivadas devem fornecer sua própria implementação.

class Shape {
public:
    virtual double area() = 0;  // Função virtual pura
    virtual ~Shape() = default;
};

Ao contrário das funções virtuais regulares que fornecem um comportamento padrão, as funções virtuais puras definem um contrato: qualquer classe derivada concreta deve implementar esta função para ser instanciável. A classe base simplesmente declara o que deve ser feito, não como.

class Circle : public Shape {
    double radius;
public:
    Circle(double r) : radius(r) {}
    
    double area() override {
        return 3.14159 * radius * radius;
    }
};

class Rectangle : public Shape {
    double width, height;
public:
    Rectangle(double w, double h) : width(w), height(h) {}
    
    double area() override {
        return width * height;
    }
};

Tanto Circle quanto Rectangle devem implementar area() porque ela é puramente virtual em Shape. Se uma classe derivada falhar em implementar todas as funções puramente virtuais, ela também se torna abstrata e não pode ser instanciada.

Funções virtuais puras são essenciais quando a classe base não pode fornecer uma implementação padrão significativa. Cada forma tem uma área, mas não há uma maneira sensata de calcular a "área de uma forma genérica" sem conhecer o tipo específico da forma.

challenge icon

Desafio

Fácil

Vamos construir um sistema de processamento de pagamentos que demonstra como funções virtuais puras impõem um contrato entre diferentes métodos de pagamento. Você criará uma classe base abstrata que define o que cada processador de pagamento deve fazer e, em seguida, implementará tipos de pagamento concretos que cumprem esse contrato.

Você organizará seu código em três arquivos:

  • PaymentProcessor.h: Defina uma classe abstrata PaymentProcessor que serve como modelo para todos os métodos de pagamento. Esta classe deve ter:
    • Um membro protegido std::string accountId
    • Um construtor que inicializa o ID da conta
    • Um método virtual puro processPayment(double amount) — cada tipo de pagamento deve implementar isso de forma diferente
    • Um método virtual puro getProcessorName() que retorna uma std::string
    • Um destrutor virtual
  • PaymentMethods.h: Implemente dois processadores de pagamento concretos que herdam de PaymentProcessor:

    CreditCardProcessor:

    • Um membro privado double feePercentage (a taxa de transação)
    • Um construtor que recebe o ID da conta e a porcentagem da taxa
    • Implemente processPayment() para calcular a taxa (amount * feePercentage / 100) e, em seguida, imprimir:
      Credit Card [<accountId>]: Charged Credit Card [<accountId>]: Charged $<amount> (Fee: $<fee>)lt;amount> (Fee: Credit Card [<accountId>]: Charged $<amount> (Fee: $<fee>)lt;fee>)
    • Implemente getProcessorName() para retornar "CreditCard"

    BankTransferProcessor:

    • Um membro privado std::string bankName
    • Um construtor que recebe o ID da conta e o nome do banco
    • Implemente processPayment() para imprimir:
      Bank Transfer [<accountId>] via <bankName>: Transferred Bank Transfer [<accountId>] via <bankName>: Transferred $<amount>lt;amount>
    • Implemente getProcessorName() para retornar "BankTransfer"
  • main.cpp: Leia quatro entradas (cada uma em uma linha separada):
    1. ID da conta do cartão de crédito
    2. Porcentagem da taxa do cartão de crédito (double)
    3. ID da conta bancária
    4. Nome do banco

    Crie ambos os processadores de pagamento e armazene-os em um array de ponteiros PaymentProcessor*. Processe um pagamento de 100.0 através de cada processador, exibindo o nome do processador antes de cada transação:

    Processing with <processorName>:
    <processPayment output>

    Imprima uma linha em branco entre os processadores. Limpe seus objetos alocados dinamicamente quando terminar.

Por exemplo, com as entradas CC-4521, 2.5, BA-7890 e National Bank:

Processing with CreditCard:
Credit Card [CC-4521]: Charged $100 (Fee: $2.5)

Processing with BankTransfer:
Bank Transfer [BA-7890] via National Bank: Transferred $100

Como PaymentProcessor possui funções virtuais puras, você não pode instanciá-lo diretamente — apenas as implementações concretas que cumprem o contrato podem ser criadas. Isso garante que cada método de pagamento forneça sua própria lógica de processamento específica.

Folha de consulta

Uma função virtual pura é declarada atribuindo = 0 a uma função virtual, indicando que ela não possui implementação na classe base:

class Shape {
public:
    virtual double area() = 0;  // Função virtual pura
    virtual ~Shape() = default;
};

Funções virtuais puras criam um contrato que as classes derivadas devem implementar. A classe base define o que deve ser feito, não como.

Classes derivadas devem sobrescrever todas as funções virtuais puras para serem instanciáveis:

class Circle : public Shape {
    double radius;
public:
    Circle(double r) : radius(r) {}
    
    double area() override {
        return 3.14159 * radius * radius;
    }
};

Se uma classe derivada não implementar todas as funções virtuais puras, ela também se torna abstrata e não pode ser instanciada.

Use funções virtuais puras quando a classe base não puder fornecer uma implementação padrão significativa.

Experimente você mesmo

#include <iostream>
#include <string>
#include "PaymentMethods.h"

using namespace std;

int main() {
    // Ler as entradas
    string ccAccountId;
    double feePercentage;
    string bankAccountId;
    string bankName;
    
    getline(cin, ccAccountId);
    cin >> feePercentage;
    cin.ignore();
    getline(cin, bankAccountId);
    getline(cin, bankName);
    
    // TODO: Criar um array de ponteiros PaymentProcessor* com 2 elementos
    
    // TODO: Criar objetos CreditCardProcessor e BankTransferProcessor
    // e armazená-los no array
    
    // TODO: Percorrer o array e para cada processador:
    // 1. Imprimir "Processing with <processorName>:"
    // 2. Chamar processPayment com o valor 100.0
    // 3. Imprimir uma linha em branco entre os processadores (não após o último)
    
    // TODO: Limpar os objetos alocados dinamicamente
    
    return 0;
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos