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 comuns, 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 não implementar todas as funções puramente virtuais, ela também se tornará abstrata e não poderá ser instanciada.
Funções virtuais puras são essenciais quando a classe-base não pode fornecer uma implementação padrão significativa. Toda 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 criar 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 todo processador de pagamentos deve fazer e, em seguida, implementará tipos concretos de pagamento 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. Essa classe deve ter:- Um membro
std::string accountIdprotegido - Um construtor que inicializa o ID da conta
- Um método virtual puro
processPayment(double amount): cada tipo de pagamento deve implementá-lo de forma diferente - Um método virtual puro
getProcessorName()que retorna umastd::string - Um destrutor virtual
- Um membro
PaymentMethods.h: Implemente dois processadores de pagamento concretos que herdam dePaymentProcessor:CreditCardProcessor:- Um membro privado
double feePercentage(a taxa da transação) - Um construtor que recebe o ID da conta e o percentual da taxa
- Implemente
processPayment()para calcular a taxa (amount * feePercentage / 100) e, em seguida, imprimir:Credit Card [<accountId>]: Charged $<amount> (Fee: $<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 $<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
- Percentual 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.0por meio 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. Libere os objetos alocados dinamicamente ao 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á-la diretamente: somente 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 específica de processamento.
Experimente você mesmo
#include <iostream>
#include <string>
#include "PaymentMethods.h"
using namespace std;
int main() {
// Ler entradas
string ccAccountId;
double feePercentage;
string bankAccountId;
string bankName;
getline(cin, ccAccountId);
cin >> feePercentage;
cin.ignore();
getline(cin, bankAccountId);
getline(cin, bankName);
// TODO: Crie um array de ponteiros PaymentProcessor* com 2 elementos
// TODO: Crie objetos CreditCardProcessor e BankTransferProcessor
// e armazene-os no array
// TODO: Percorra o array e para cada processador:
// 1. Print "Processing with <processorName>:"
// 2. Chame processPayment com o valor 100.0
// 3. Imprima uma linha em branco entre os processadores (não após o último)
// TODO: Limpe 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 POO
Arquivos externosBuild e compilação em C++Arquivos de cabeçalho e arquivos-fonteNamespaces e escopoIntrodução à POO em C++Classes vs. objetosO ponteiro 'this'Métodos (funções membro)Atributos (membros de dados)Noções básicas de construtores e destruidoresRevisão - calculadora simples4Propriedades de Classe
Membros de Instância vs. EstáticosGetters e SettersFunções Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRevisão - Gerenciador de Contas Bancárias7Herança
Herança básicaNíveis de acesso da herançaOrdem de chamada de construtor e destrutorSobrescrita de métodosFunções virtuais e VTableHerança múltiplaHerança virtualRevisão - Hierarquia de funcionários10Visão geral da STL
Visão geral e filosofia da STLContêineres da STLIteradoresAlgoritmos da STLFunctors e expressões lambdaRecapitulação - Frequência de palavras13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonFactory e Abstract FactoryPadrão BuilderPadrão ObserverPadrão Strategy2Gerenciamento de Memória
Memória Stack vs. HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Ponteiros Inteligentes em C++RAII em C++Revisão - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de acesso em C++Especificadores de acesso em profundidadeOcultação de informaçõesStruct vs ClasseClasses aninhadas e internasRecapitulação - Sistema de registros de alunos8Polimorfismo
Polimorfismo em compilação vs. execuçãoSobrecarga de funçõesFunções virtuais revisadasFunções virtuais purasClasses abstratasDesign de interfaces em C++Conversão dinâmica e RTTIRecapitulação — Calculadora de formas11Conceitos Avançados de OOP
Composição vs. HerançaMixins via CRTPIdioma PimplApagamento de TiposClasses Enum e Tipagem ForteTratamento de Exceções em OOPHierarquias de Exceções Personalizadas14Padrões de Projeto — Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeRAII como Padrão3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentoListas de Inicialização do ConstrutorConstrutores DelegantesAprofundamento em DestrutoresRegra dos Três / Cinco / ZeroRevisão - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de FluxoSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçõesTemplates de ClassesEspecialização de TemplatesTemplates VariádicosNoções Básicas de SFINAE e Traits de TiposRecapitulação - Contêiner GenéricoPratique por conta própria: Compilador de C++ online