Menu
Coddy logo textTech

Fonctions virtuelles pures

Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy — leçon 59 sur 104.

Une fonction virtuelle pure est une fonction virtuelle qui n'a pas d'implémentation dans la classe de base. Elle est déclarée en affectant = 0 à la déclaration de la fonction. Cela indique au compilateur que les classes dérivées doivent fournir leur propre implémentation.

class Shape {
public:
    virtual double area() = 0;  // Fonction virtuelle pure
    virtual ~Shape() = default;
};

Contrairement aux fonctions virtuelles classiques qui fournissent un comportement par défaut, les fonctions virtuelles pures définissent un contrat : toute classe dérivée concrète doit implémenter cette fonction pour pouvoir être instanciée. La classe de base déclare simplement ce qui doit être fait, et non comment.

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

Les deux classes Circle et Rectangle doivent implémenter area() car elle est virtuelle pure dans Shape. Si une classe dérivée ne parvient pas à implémenter toutes les fonctions virtuelles pures, elle devient également abstraite et ne peut pas être instanciée.

Les fonctions virtuelles pures sont essentielles lorsque la classe de base ne peut pas fournir d'implémentation par défaut significative. Chaque forme a une aire, mais il n'y a pas de moyen sensé de calculer « l'aire d'une forme générique » sans connaître le type de forme spécifique.

challenge icon

Défi

Facile

Construisons un système de traitement des paiements qui démontre comment les fonctions virtuelles pures imposent un contrat à travers différentes méthodes de paiement. Vous allez créer une classe de base abstraite qui définit ce que chaque processeur de paiement doit faire, puis implémenter des types de paiement concrets qui remplissent ce contrat.

Vous organiserez votre code sur trois fichiers :

  • PaymentProcessor.h : Définissez une classe abstraite PaymentProcessor qui sert de modèle pour toutes les méthodes de paiement. Cette classe doit avoir :
    • Un membre protégé std::string accountId
    • Un constructeur qui initialise l'identifiant du compte
    • Une méthode virtuelle pure processPayment(double amount) — chaque type de paiement doit l'implémenter différemment
    • Une méthode virtuelle pure getProcessorName() qui retourne un std::string
    • Un destructeur virtuel
  • PaymentMethods.h : Implémentez deux processeurs de paiement concrets qui héritent de PaymentProcessor :

    CreditCardProcessor :

    • Un membre privé double feePercentage (le taux de frais de transaction)
    • Un constructeur prenant l'identifiant du compte et le pourcentage de frais
    • Implémentez processPayment() pour calculer les frais (amount * feePercentage / 100), puis affichez :
      Credit Card [<accountId>]: Charged Credit Card [<accountId>]: Charged $<amount> (Fee: $<fee>)lt;amount> (Fee: Credit Card [<accountId>]: Charged $<amount> (Fee: $<fee>)lt;fee>)
    • Implémentez getProcessorName() pour retourner "CreditCard"

    BankTransferProcessor :

    • Un membre privé std::string bankName
    • Un constructeur prenant l'identifiant du compte et le nom de la banque
    • Implémentez processPayment() pour afficher :
      Bank Transfer [<accountId>] via <bankName>: Transferred Bank Transfer [<accountId>] via <bankName>: Transferred $<amount>lt;amount>
    • Implémentez getProcessorName() pour retourner "BankTransfer"
  • main.cpp : Lisez quatre entrées (chacune sur une ligne séparée) :
    1. L'identifiant du compte de carte de crédit
    2. Le pourcentage de frais de la carte de crédit (double)
    3. L'identifiant du compte bancaire
    4. Le nom de la banque

    Créez les deux processeurs de paiement et stockez-les dans un tableau de pointeurs PaymentProcessor*. Traitez un paiement de 100.0 via chaque processeur, en affichant le nom du processeur avant chaque transaction :

    Processing with <processorName>:
    <processPayment output>

    Affichez une ligne vide entre les processeurs. Libérez vos objets alloués dynamiquement une fois terminé.

Par exemple, avec les entrées CC-4521, 2.5, BA-7890, et 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

Parce que PaymentProcessor possède des fonctions virtuelles pures, vous ne pouvez pas l'instancier directement — seules les implémentations concrètes qui remplissent le contrat peuvent être créées. Cela garantit que chaque méthode de paiement fournit sa propre logique de traitement spécifique.

Aide-mémoire

Une fonction virtuelle pure est déclarée en assignant = 0 à une fonction virtuelle, indiquant qu'elle n'a pas d'implémentation dans la classe de base :

class Shape {
public:
    virtual double area() = 0;  // Fonction virtuelle pure
    virtual ~Shape() = default;
};

Les fonctions virtuelles pures créent un contrat que les classes dérivées doivent implémenter. La classe de base définit ce qui doit être fait, pas comment.

Les classes dérivées doivent redéfinir toutes les fonctions virtuelles pures pour pouvoir être instanciées :

class Circle : public Shape {
    double radius;
public:
    Circle(double r) : radius(r) {}
    
    double area() override {
        return 3.14159 * radius * radius;
    }
};

Si une classe dérivée n'implémente pas toutes les fonctions virtuelles pures, elle devient également abstraite et ne peut pas être instanciée.

Utilisez des fonctions virtuelles pures lorsque la classe de base ne peut pas fournir d'implémentation par défaut significative.

Essayez vous-même

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

using namespace std;

int main() {
    // Lire les entrées
    string ccAccountId;
    double feePercentage;
    string bankAccountId;
    string bankName;
    
    getline(cin, ccAccountId);
    cin >> feePercentage;
    cin.ignore();
    getline(cin, bankAccountId);
    getline(cin, bankName);
    
    // TODO: Créer un tableau de pointeurs PaymentProcessor* avec 2 éléments
    
    // TODO: Créer des objets CreditCardProcessor et BankTransferProcessor
    // et les stocker dans le tableau
    
    // TODO: Parcourir le tableau et pour chaque processeur :
    // 1. Afficher "Processing with <processorName>:"
    // 2. Appeler processPayment avec le montant 100.0
    // 3. Afficher une ligne vide entre les processeurs (pas après le dernier)
    
    // TODO: Nettoyer les objets alloués dynamiquement
    
    return 0;
}
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation Orientée Objet