Menu
Coddy logo textTech

Funzioni virtuali pure

Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 59 di 104.

Una funzione virtuale pura è una funzione virtuale che non ha un'implementazione nella classe base. Si dichiara assegnando = 0 alla dichiarazione della funzione. Questo indica al compilatore che le classi derivate devono fornire la propria implementazione.

class Shape {
public:
    virtual double area() = 0;  // Funzione virtuale pura
    virtual ~Shape() = default;
};

A differenza delle normali funzioni virtuali che forniscono un comportamento predefinito, le funzioni virtuali pure definiscono un contratto: qualsiasi classe derivata concreta deve implementare questa funzione per poter essere istanziata. La classe base dichiara semplicemente che cosa deve essere fatto, non come.

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;
    }
};

Sia Circle che Rectangle devono implementare area() perché è virtuale pura in Shape. Se una classe derivata non implementa tutte le funzioni virtuali pure, diventa anch'essa astratta e non può essere istanziata.

Le funzioni virtuali pure sono essenziali quando la classe base non può fornire un'implementazione predefinita significativa. Ogni forma ha un'area, ma non c'è un modo ragionevole per calcolare l'«area di una forma generica» senza conoscere il tipo specifico della forma.

challenge icon

Sfida

Facile

Costruiamo un sistema di elaborazione dei pagamenti che mostri come le funzioni virtuali pure impongano un contratto per i diversi metodi di pagamento. Creerai una classe base astratta che definisce ciò che ogni elaboratore di pagamenti deve fare, poi implementerai tipi di pagamento concreti che rispettano tale contratto.

Organizzerai il codice in tre file:

  • PaymentProcessor.h: definisci una classe astratta PaymentProcessor che funge da modello per tutti i metodi di pagamento. Questa classe deve avere:
    • Un membro protetto std::string accountId
    • Un costruttore che inizializza l'ID del conto
    • Un metodo virtuale puro processPayment(double amount): ogni tipo di pagamento deve implementarlo in modo diverso
    • Un metodo virtuale puro getProcessorName() che restituisce un std::string
    • Un distruttore virtuale
  • PaymentMethods.h: implementa due elaboratori di pagamenti concreti che ereditano da PaymentProcessor:

    CreditCardProcessor:

    • Un membro privato double feePercentage (l'aliquota della commissione di transazione)
    • Un costruttore che accetta l'ID del conto e la percentuale della commissione
    • Implementa processPayment() per calcolare la commissione (amount * feePercentage / 100), quindi stampa:
      Credit Card [<accountId>]: Charged $<amount> (Fee: $<fee>)
    • Implementa getProcessorName() in modo che restituisca "CreditCard"

    BankTransferProcessor:

    • Un membro privato std::string bankName
    • Un costruttore che accetta l'ID del conto e il nome della banca
    • Implementa processPayment() in modo che stampi:
      Bank Transfer [<accountId>] via <bankName>: Transferred $<amount>
    • Implementa getProcessorName() in modo che restituisca "BankTransfer"
  • main.cpp: leggi quattro input (ciascuno su una riga separata):
    1. ID del conto della carta di credito
    2. Percentuale della commissione della carta di credito (double)
    3. ID del conto bancario
    4. Nome della banca

    Crea entrambi gli elaboratori di pagamenti e memorizzali in un array di puntatori PaymentProcessor*. Elabora un pagamento di 100.0 con ciascun elaboratore, mostrando il nome dell'elaboratore prima di ogni transazione:

    Processing with <processorName>:
    <processPayment output>

    Stampa una riga vuota tra gli elaboratori. Al termine, libera gli oggetti allocati dinamicamente.

Per esempio, con gli input 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 $100

Poiché PaymentProcessor ha funzioni virtuali pure, non puoi istanziarla direttamente: si possono creare solo le implementazioni concrete che rispettano il contratto. In questo modo, ogni metodo di pagamento fornisce la propria logica di elaborazione specifica.

Provalo tu

#include <iostream>
#include <string>
#include "PaymentMethods.h"

using namespace std;

int main() {
    // Leggi i dati in input
    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 di puntatori PaymentProcessor* con 2 elementi
    
    // TODO: Crea oggetti CreditCardProcessor e BankTransferProcessor
    // e memorizzali nell'array
    
    // TODO: Scorri l'array e, per ogni processore:
    // 1. Stampa "Processing with <processorName>:"
    // 2. Chiama processPayment con l'importo 100.0
    // 3. Stampa una riga vuota tra i processori (ma non dopo l'ultimo)
    
    // TODO: Libera la memoria degli oggetti allocati dinamicamente
    
    return 0;
}
quiz iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Compilatore C++ online