Menu
Coddy logo textTech

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.

challenge icon

Sfida

Facile

Costruiamo 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 base Calculator che rappresenta qualsiasi strategia di calcolo:
    • Un metodo virtuale calculate(int a, int b) che restituisce un int e stampa: Base calculation: <a> ? <b> (restituendo 0)
    • Un distruttore virtuale
  • Operations.h: Definisci due classi calcolatrice derivate che sovrascrivono il comportamento di calcolo:
    • Adder: Sovrascrivi calculate() affinché stampi Adding: <a> + <b> e restituisca la somma
    • Multiplier: Sovrascrivi calculate() affinché stampi Multiplying: <a> * <b> e restituisca il prodotto
    Entrambe le classi devono usare la parola chiave 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 oggetto Calculator base, un oggetto Adder e un oggetto Multiplier. Scorri l'array e chiama calculate() 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: 30

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