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.
Défi
FacileConstruisons 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 abstraitePaymentProcessorqui 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 unstd::string - Un destructeur virtuel
- Un membre protégé
PaymentMethods.h: Implémentez deux processeurs de paiement concrets qui héritent dePaymentProcessor: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"
- Un membre privé
main.cpp: Lisez quatre entrées (chacune sur une ligne séparée) :- L'identifiant du compte de carte de crédit
- Le pourcentage de frais de la carte de crédit (double)
- L'identifiant du compte bancaire
- Le nom de la banque
Créez les deux processeurs de paiement et stockez-les dans un tableau de pointeurs
PaymentProcessor*. Traitez un paiement de100.0via 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 $100Parce 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;
}
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
1Fondamentaux de la POO
Fichiers externesBuild et Compilation C++Fichiers d'en-tête et Fichiers sourceEspaces de noms et PortéeIntroduction à la POO en C++Classes vs ObjetsLe pointeur 'this'Méthodes (Fonctions membres)Attributs (Membres de données)Bases des Ctors et DtorsRécapitulatif - Calculatrice simple4Propriétés de classe
Membres d'instance vs membres statiquesGetters et SettersFonctions membres constMot-clé mutableMéthodes et variables statiquesFonctions et classes amiesRécapitulatif - Gestionnaire de compte bancaire7Héritage
Héritage de baseNiveaux d'accès à l'héritageOrdre d'appel des Ctor et DtorRedéfinition de méthodesFonctions virtuelles et VTableHéritage multipleHéritage virtuelRécapitulatif - Hiérarchie des employés10Présentation de la STL
Présentation et philosophie de la STLConteneurs de la STLItérateursAlgorithmes de la STLFoncteurs et expressions lambdaRécapitulatif - Fréquence des mots13Patrons de conception, Partie 1
Introduction aux patrons de conceptionPatron SingletonFabrique & Fabrique abstraitePatron MonteurPatron ObservateurPatron Stratégie2Gestion de la mémoire
Mémoire Pile vs TasPointeurs et RéférencesMémoire dynamique (new/delete)Smart Pointers en C++RAII en C++Récapitulatif - Gestionnaire de tableaux dynamiques5Encapsulation
Spécificateurs d'accès en C++Spécificateurs d'accès en profondeurMasquage d'informationStruct vs ClassClasses imbriquées et internesRécapitulatif - Système de gestion d'étudiants8Polymorphisme
Polymorphisme : Compilation vs ExécutionSurcharge de fonctionsRetour sur les fonctions virtuellesFonctions virtuelles puresClasses abstraitesConception d'interfaces en C++Dynamic Casting & RTTIRécapitulatif - Calculateur de formes11Concepts avancés de la POO
Composition vs HéritageMixins via CRTPIdiome PimplEffacement de typeEnum Classes & Typage fortGestion des exceptions en POOHiérarchies d'exceptions personnalisées14Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositeRAII en tant que patron3Constructeurs et Destructeurs
Constructeur par défautConstructeur paramétréConstructeur de copieConstructeur de déplacementListes d'initialisation du constructeurConstructeurs déléguésAnalyse approfondie du destructeurRègle de trois / cinq / zéroRécapitulatif - Classe String6Surcharge d'opérateurs
Introduction à la surcharge d'opérateursSurcharge des opérateurs arithmétiquesSurcharge des opérateurs de comparaisonOpérateurs de fluxSurcharge de l'opérateur d'affectationSurcharge des opérateurs [] et ()Opérateurs de conversion de typeRécapitulatif - Classe Matrix9Templates
Templates de fonctionsTemplates de classesSpécialisation de templatesTemplates variadiquesBases de SFINAE & Type TraitsRécapitulatif - Conteneur générique