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 regulares 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 puramente virtual en Shape. Si una clase derivada no implementa todas las funciones virtuales puras, también se vuelve abstracta y no puede ser instanciada.
Las funciones virtuales puras son esenciales cuando la clase base no puede proporcionar una implementación predeterminada significativa. Cada forma tiene un área, pero no hay una forma sensata de calcular el "área de una forma genérica" sin conocer el tipo de forma específico.
Desafío
FácilVamos a construir un sistema de procesamiento de pagos que demuestre cómo las funciones virtuales puras imponen un contrato a través de diferentes métodos de pago. Crearás una clase base abstracta que define lo que cada procesador de pagos debe hacer, y luego implementarás tipos de pago concretos que cumplan con ese contrato.
Organizarás tu código en tres archivos:
PaymentProcessor.h: Define una clase abstractaPaymentProcessorque sirva como plano 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 implementar esto de manera diferente - Un método virtual puro
getProcessorName()que devuelva unstd::string - Un destructor virtual
- Un miembro protegido
PaymentMethods.h: Implementa dos procesadores de pago concretos que hereden dePaymentProcessor:CreditCardProcessor:- Un miembro privado
double feePercentage(la tasa de comisión por 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 luego imprime:Credit Card [<accountId>]: Charged Credit Card [<accountId>]: Charged $<amount> (Fee: $<fee>)lt;amount> (Fee: Credit Card [<accountId>]: Charged $<amount> (Fee: $<fee>)lt;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 Bank Transfer [<accountId>] via <bankName>: Transferred $<amount>lt;amount> - Implementa
getProcessorName()para devolver"BankTransfer"
- Un miembro privado
main.cpp: Lee cuatro entradas (cada una en una línea separada):- ID de cuenta de tarjeta de crédito
- Porcentaje de comisión de la tarjeta de crédito (double)
- ID de cuenta bancaria
- Nombre del banco
Crea ambos procesadores de pago y almacénalos en un arreglo de punteros
PaymentProcessor*. Procesa un pago de100.0a través de cada procesador, mostrando el nombre del procesador antes de cada transacción:Processing with <processorName>: <processPayment output>Imprime una línea en blanco entre procesadores. Limpia tus objetos asignados dinámicamente cuando termines.
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 $100Debido a que PaymentProcessor tiene funciones virtuales puras, no puedes instanciarlo directamente; solo se pueden crear las implementaciones concretas que cumplen con el contrato. Esto asegura que cada método de pago proporcione su propia lógica de procesamiento específica.
Hoja de referencia
Una función virtual pura se declara asignando = 0 a una función virtual, indicando que no tiene implementación en la clase base:
class Shape {
public:
virtual double area() = 0; // Función virtual pura
virtual ~Shape() = default;
};Las funciones virtuales puras crean un contrato que las clases derivadas deben implementar. La clase base define qué debe hacerse, no cómo.
Las clases derivadas deben sobrescribir todas las funciones virtuales puras para poder ser instanciadas:
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
double area() override {
return 3.14159 * radius * radius;
}
};Si una clase derivada no implementa todas las funciones virtuales puras, también se vuelve abstracta y no puede ser instanciada.
Utilice funciones virtuales puras cuando la clase base no pueda proporcionar una implementación predeterminada significativa.
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: Crear un arreglo de punteros PaymentProcessor* con 2 elementos
// TODO: Crear objetos CreditCardProcessor y BankTransferProcessor
// y almacenarlos en el arreglo
// TODO: Recorrer el arreglo y para cada procesador:
// 1. Imprimir "Processing with <processorName>:"
// 2. Llamar a processPayment con el monto 100.0
// 3. Imprimir una línea en blanco entre procesadores (no después del último)
// TODO: Limpiar 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érico