Funciones virtuales puras
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy. Lección 59 de 104.
Una función virtual pura es una función virtual que no tiene implementación en la clase base. Se declara asignando = 0 a la declaración de la función. Esto le indica al compilador que las clases derivadas deben proporcionar su propia implementación.
class Shape {
public:
virtual double area() = 0; // Función virtual pura
virtual ~Shape() = default;
};A diferencia de las funciones virtuales normales, que proporcionan un comportamiento predeterminado, las funciones virtuales puras definen un contrato: cualquier clase derivada concreta debe implementar esta función para poder ser instanciada. La clase base simplemente declara qué debe hacerse, no cómo.
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 como Rectangle deben implementar area() porque es una función virtual pura en Shape. Si una clase derivada no implementa todas las funciones virtuales puras, también se vuelve abstracta y no se puede instanciar.
Las funciones virtuales puras son esenciales cuando la clase base no puede proporcionar una implementación predeterminada significativa. Toda figura tiene un área, pero no hay una forma sensata de calcular el «área de una figura genérica» sin conocer el tipo específico de figura.
Desafío
FácilConstruyamos un sistema de procesamiento de pagos que demuestre cómo las funciones virtuales puras hacen cumplir un contrato entre distintos métodos de pago. Crearás una clase base abstracta que define lo que todo procesador de pagos debe hacer y, después, implementarás tipos de pago concretos que cumplan ese contrato.
Organizarás tu código en tres archivos:
PaymentProcessor.h: Define una clase abstractaPaymentProcessorque sirve como modelo para todos los métodos de pago. Esta clase debe tener:- Un miembro protegido
std::string accountId - Un constructor que inicialice el ID de la cuenta
- Un método virtual puro
processPayment(double amount): cada tipo de pago debe implementarlo de forma diferente - Un método virtual puro
getProcessorName()que devuelva unstd::string - Un destructor virtual
- Un miembro protegido
PaymentMethods.h: Implementa dos procesadores de pagos concretos que hereden dePaymentProcessor:CreditCardProcessor:- Un miembro privado
double feePercentage(la tasa de comisión de la transacción) - Un constructor que reciba el ID de la cuenta y el porcentaje de comisión
- Implementa
processPayment()para calcular la comisión (amount * feePercentage / 100) y, después, imprimir:Credit Card [<accountId>]: Charged $<amount> (Fee: $<fee>) - Implementa
getProcessorName()para devolver"CreditCard"
BankTransferProcessor:- Un miembro privado
std::string bankName - Un constructor que reciba el ID de la cuenta y el nombre del banco
- Implementa
processPayment()para imprimir:Bank Transfer [<accountId>] via <bankName>: Transferred $<amount> - Implementa
getProcessorName()para devolver"BankTransfer"
- Un miembro privado
main.cpp: Lee cuatro entradas (cada una en una línea separada):- ID de la cuenta de la tarjeta de crédito
- Porcentaje de comisión de la tarjeta de crédito (double)
- ID de la cuenta bancaria
- Nombre del banco
Crea ambos procesadores de pagos y guárdalos en un arreglo de punteros
PaymentProcessor*. Procesa un pago de100.0mediante cada procesador y muestra el nombre del procesador antes de cada transacción:Processing with <processorName>: <processPayment output>Imprime una línea en blanco entre los procesadores. Libera los objetos asignados dinámicamente cuando hayas terminado.
Por ejemplo, con las entradas CC-4521, 2.5, BA-7890 y 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 tiene funciones virtuales puras, no puedes crear una instancia directamente: solo se pueden crear las implementaciones concretas que cumplan el contrato. Esto garantiza que cada método de pago proporcione su propia lógica de procesamiento específica.
Pruébalo tú mismo
#include <iostream>
#include <string>
#include "PaymentMethods.h"
using namespace std;
int main() {
// Leer entradas
string ccAccountId;
double feePercentage;
string bankAccountId;
string bankName;
getline(cin, ccAccountId);
cin >> feePercentage;
cin.ignore();
getline(cin, bankAccountId);
getline(cin, bankName);
// TODO: Crea un array de punteros PaymentProcessor* con 2 elementos
// TODO: Crea objetos CreditCardProcessor y BankTransferProcessor
// y almacénalos en el array
// TODO: Recorre el array y para cada procesador:
// 1. Print "Processing with <processorName>:"
// 2. Llama a processPayment con amount 100.0
// 3. Imprime una línea en blanco entre procesadores (no después del último)
// TODO: Limpia los objetos asignados dinámicamente
return 0;
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño - Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y Destructores
Constructor por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genéricoPractica por tu cuenta: Compilador de C++ online