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.
Desafio
FácilVamos 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 abstrataPaymentProcessorque 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 umastd::string - Um destrutor virtual
- Um membro protegido
PaymentMethods.h: Implemente dois processadores de pagamento concretos que herdam dePaymentProcessor: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"
- Um membro privado
main.cpp: Leia quatro entradas (cada uma em uma linha separada):- ID da conta do cartão de crédito
- Porcentagem da taxa do cartão de crédito (double)
- ID da conta bancária
- Nome do banco
Crie ambos os processadores de pagamento e armazene-os em um array de ponteiros
PaymentProcessor*. Processe um pagamento de100.0atravé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 $100Como 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;
}
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
1Fundamentos de OOP
Arquivos ExternosBuild e Compilação em C++Arquivos Header e SourceNamespaces e EscopoIntrodução a OOP em C++Classes vs ObjetosO Ponteiro 'this'Métodos (Funções Membro)Atributos (Membros de Dados)Básico de Ctors e DtorsRecapitulação - Calculadora Simples4Propriedades de Classe
Membros de Instância vs EstáticosGetters e SettersFunções de Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRecapitulação - Gerenciador de Conta Bancária7Herança
Herança BásicaNíveis de Acesso na HerançaOrdem de Chamada de Ctor e DtorSobrescrita de MétodosFunções Virtuais e VTableHerança MúltiplaHerança VirtualRecapitulação - Hierarquia de Funcionários2Gerenciamento de Memória
Memória Stack vs HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Smart Pointers em C++RAII em C++Recapitulação - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de Acesso em C++Especificadores de Acesso em DetalhesOcultação de InformaçãoStruct vs ClassClasses Aninhadas e InternasRecapitulação - Sistema de Registros de Alunos8Polimorfismo
Polimorfismo de Compilação vs Tempo de ExecuçãoSobrecarga de FunçõesFunções Virtuais RevisitadasFunções Virtuais PurasClasses AbstratasDesign de Interfaces em C++Dynamic Casting e RTTIRecapitulação - Calculadora de Formas3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentaçãoListas de Inicialização do ConstrutorConstrutores DelegadosMergulho Profundo em DestrutoresRegra dos Três / Cinco / ZeroRecapitulação - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de StreamSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçãoTemplates de ClasseEspecialização de TemplatesTemplates VariádicosFundamentos de SFINAE e Type TraitsRecapitulação - Container Genérico