Menu
Coddy logo textTech

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.

challenge icon

Desafío

Fácil

Vamos 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 abstracta PaymentProcessor que 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 un std::string
    • Un destructor virtual
  • PaymentMethods.h: Implementa dos procesadores de pago concretos que hereden de PaymentProcessor:

    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"
  • main.cpp: Lee cuatro entradas (cada una en una línea separada):
    1. ID de cuenta de tarjeta de crédito
    2. Porcentaje de comisión de la tarjeta de crédito (double)
    3. ID de cuenta bancaria
    4. Nombre del banco

    Crea ambos procesadores de pago y almacénalos en un arreglo de punteros PaymentProcessor*. Procesa un pago de 100.0 a 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 $100

Debido 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;
}
quiz iconPonte a prueba

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