Polimorfismo a compile time e a runtime
Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 56 di 104.
Polimorfismo significa «molte forme» ed è un concetto fondamentale della programmazione orientata agli oggetti (OOP), che consente di trattare gli oggetti in modo uniforme pur facendoli comportare in maniera diversa. C++ supporta due tipi distinti di polimorfismo, ciascuno risolto in una fase diversa dell'esecuzione del programma.
Il polimorfismo statico (chiamato anche polimorfismo a tempo di compilazione) viene risolto dal compilatore prima dell'esecuzione del programma. Il compilatore determina esattamente quale funzione chiamare in base alla firma della funzione. Questo include l'overloading delle funzioni e i template:
void print(int x) { std::cout << "Integer: " << x << std::endl; }
void print(double x) { std::cout << "Double: " << x << std::endl; }
print(5); // Il compilatore sceglie print(int)
print(3.14); // Il compilatore sceglie print(double)Il polimorfismo a runtime (chiamato anche polimorfismo dinamico) viene risolto mentre il programma è in esecuzione. La decisione su quale funzione chiamare dipende dal tipo effettivo dell'oggetto, non dal tipo del puntatore o del riferimento. Ciò si ottiene tramite funzioni virtuali:
class Shape {
public:
virtual void draw() { std::cout << "Drawing shape" << std::endl; }
};
class Circle : public Shape {
public:
void draw() override { std::cout << "Drawing circle" << std::endl; }
};
Shape* s = new Circle();
s->draw(); // Determinato in fase di esecuzione: "Drawing circle"Il compromesso fondamentale: il polimorfismo statico non comporta alcun sovraccarico in fase di esecuzione, poiché le decisioni vengono prese durante la compilazione, mentre il polimorfismo dinamico comporta un piccolo costo (ricerca nella vtable), ma offre maggiore flessibilità nel lavorare con oggetti i cui tipi non sono noti fino all'esecuzione.
Sfida
FacileCostruiamo un sistema di calcolatrice che dimostri entrambi i tipi di polimorfismo affiancati. Creerai un sistema in cui il polimorfismo a tempo di compilazione gestisce diversi tipi di input tramite l'overload delle funzioni, mentre il polimorfismo a tempo di esecuzione consente di sostituire dinamicamente diverse strategie di calcolo.
Organizzerai il codice in tre file:
Calculator.h: Definisci una classe baseCalculatorche rappresenta qualsiasi strategia di calcolo:- Un metodo virtuale
calculate(int a, int b)che restituisce uninte stampa:Base calculation: <a> ? <b>(restituendo 0) - Un distruttore virtuale
- Un metodo virtuale
Operations.h: Definisci due classi calcolatrice derivate che sovrascrivono il comportamento di calcolo:Adder: Sovrascrivicalculate()affinché stampiAdding: <a> + <b>e restituisca la sommaMultiplier: Sovrascrivicalculate()affinché stampiMultiplying: <a> * <b>e restituisca il prodotto
override.main.cpp: Crea un sistema che mostri entrambi i tipi di polimorfismo. Leggi due valori interi (ciascuno su una riga separata).Per prima cosa, dimostra il polimorfismo a tempo di compilazione creando tre funzioni
display()sovraccaricate:display(int x)stampa:Integer value: <x>display(double x)stampa:Double value: <x>display(const std::string& x)stampa:String value: <x>
Poi dimostra il polimorfismo a tempo di esecuzione creando un array di puntatori
Calculator*che contenga un oggettoCalculatorbase, un oggettoAddere un oggettoMultiplier. Scorri l'array e chiamacalculate()su ciascun oggetto usando i valori di input, stampando il risultato dopo ogni calcolo.Struttura l'output come segue:
=== Compile-Time Polymorphism === <display outputs for int, double, string> === Runtime Polymorphism === <calculate outputs with results>Per la sezione a tempo di compilazione, chiama
display()con il primo input come intero, poi come double (lo stesso valore con .5 aggiunto) e infine come stringa "Result". Al termine, libera le calcolatrici allocate dinamicamente.
Per esempio, con gli input 10 e 3:
=== Compile-Time Polymorphism ===
Integer value: 10
Double value: 10.5
String value: Result
=== Runtime Polymorphism ===
Base calculation: 10 ? 3
Result: 0
Adding: 10 + 3
Result: 13
Multiplying: 10 * 3
Result: 30Nota come il compilatore seleziona l'overload display() corretto in base al tipo dell'argomento (decisione a tempo di compilazione), mentre il metodo calculate() corretto viene determinato dal tipo effettivo dell'oggetto durante l'esecuzione tramite il meccanismo della vtable.
Provalo tu
#include <iostream>
#include <string>
#include "Calculator.h"
#include "Operations.h"
// TODO: Crea tre funzioni display() sovraccaricate:
// 1. display(int x) - stampa "Integer value: <x>"
// 2. display(double x) - stampa "Double value: <x>"
// 3. display(const std::string& x) - stampa "String value: <x>"
int main() {
// Leggi due valori interi in input
int a, b;
std::cin >> a;
std::cin >> b;
// === Compile-Time Polymorphism ===
std::cout << "=== Compile-Time Polymorphism ===" << std::endl;
// TODO: Chiama display() con:
// - a come intero
// - a come double (aggiungi 0.5 ad a)
// - la stringa "Result"
std::cout << std::endl;
// === Runtime Polymorphism ===
std::cout << "=== Runtime Polymorphism ===" << std::endl;
// TODO: Crea un array di puntatori Calculator* con 3 elementi:
// - un Calculator di base
// - un Adder
// - un Multiplier
// TODO: Recorre el arreglo y llama a calculate(a, b) en cada elemento,
// e imprime "Result: <return_value>" después de cada cálculo
// TODO: Libera la memoria asignada dinámicamente
return 0;
}
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
1Fondamenti della programmazione orientata agli oggetti
File esterniBuild e compilazione in C++File header e file sorgenteNamespace e ambitoIntroduzione alla programmazione orientata agli oggetti in C++Classi e oggetti a confrontoIl puntatore 'this'Metodi (funzioni membro)Attributi (membri dati)Fondamenti di costruttori e distruttoriRiepilogo - Calcolatrice semplice4Proprietà delle classi
Membri di istanza e staticiGetter e setterFunzioni membro constParola chiave mutableMetodi e variabili staticiFunzioni e classi friendRiepilogo - Gestore di conti bancari7Ereditarietà
Ereditarietà di baseLivelli di accesso nell’ereditarietàOrdine di chiamata di costruttori e distruttoriRidefinizione dei metodiFunzioni virtuali e VTableEreditarietà multiplaEreditarietà virtualeRiepilogo - Gerarchia dei dipendenti2Gestione della memoria
Memoria Stack vs HeapPuntatori e riferimentiMemoria dinamica (new/delete)Puntatori intelligenti in C++RAII in C++Riepilogo - Gestore di array dinamico5Incapsulamento
Specificatori di accesso in C++Specificatori di accesso in dettaglioOccultamento delle informazioniStruct vs classClassi annidate e interneRiepilogo - Sistema di registrazione degli studenti8Polimorfismo
Polimorfismo a compile time e a runtimeOverload delle funzioniFunzioni virtuali: ripassoFunzioni virtuali pureClassi astratteProgettazione delle interfacce in C++Dynamic casting e RTTIRipasso: calcolatrice di forme11Concetti avanzati di OOP
Composizione vs ereditarietàMixin tramite CRTPIdiom PimplType ErasureEnum class e tipizzazione forteGestione delle eccezioni in OOPGerarchie personalizzate di eccezioni14Pattern di progettazione - Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeRAII come pattern3Costruttori e distruttori
Costruttore predefinitoCostruttore con parametriCostruttore di copiaCostruttore di spostamentoListe di inizializzazione dei costruttoriCostruttori delegantiApprofondimento sui distruttoriRegola del Tre / Cinque / ZeroRipasso - classe String6Sovraccarico degli operatori
Introduzione al sovraccarico degli operatoriSovraccarico degli operatori aritmeticiSovraccarico degli operatori di confrontoOperatori di flussoSovraccarico dell'operatore di assegnazioneSovraccarico degli operatori [] e ()Operatori di conversione di tipoRipasso - Classe Matrix9Template
Template di funzioneTemplate di classeSpecializzazione dei templateTemplate variadiciBasi di SFINAE e dei trait di tipoRiepilogo - Contenitore genericoEsercitati da solo: Compilatore C++ online