sabato 9 maggio 2015

Modern C++ - Classi base e derivate

Benritrovati sulle pagine del corso di Modern C++.

Nelle puntate precedenti abbiamo visto cosa sono le classi e come vengono istanziati gli oggetti.

Oggi vediamo come realizzare un albero genealogico di classi ed oggetti: per prima cosa, torniamo all'esempio della nostra automobile.
Una automobile é caratterizzata da un predefinito motore (la cilindrata), da un numero di ruote, da un numero di porte e cosí via. Le varie specializzazioni di una auto potrebbero essere: macchina sportiva, station wagon, ecc. Una auto é un mezzo di locomozione: suoi correlati possono essere furgoni, autobus, motociclette, barche, aerei.

Pertanto, ha senso definire la seguente struttura:

  • Veicolo
    • VeicoloTerrestre
      • Automobile
      • Motocicletta
      • Bus
      • Tir
      • Furgone
      • Treno
    • VeicoloNavale
      • Nave
      • Sommergibile
    • VeicoloAereo 
      • Aereo
      • Elicottero
Cosa accomuna queste classi tra loro? Tutti questi tipi di veicoli sono caratterizzati da un motore che li fa muovere, sia esso elettrico o termico. Un'altra caratteristica che accomuna tutti i veicoli tra loro puó essere il colore, la velocitá massima e appunto il tipo di motore (termico o elettrico).

Prima di passare alla realizzazione pratica, pensiamo ad alcuni dettagli:
  • I veicoli sopracitati funzionano tutti allo stesso modo?
  • I veicoli sopracitati sono dotati tutti di porte?
Teoricamente si, ma praticamente no: un aereo ed un sommergibile possono accelerare e frenare, ma la modalitá con cui frenano od accelerano cambia drasticamente. Pertanto, l'azione di frenare ed accelerare é astratta, dipendente dall'implementazione.

In C++ (ma anche in altri linguaggi OOP) vengono in soccorso i metodi e le classi virtuali o astratte: a seconda di come sono dichiarate le funzioni e se esse hanno o meno una definizione (ovvero, un corpo), esse vengono dette virtual o pure virtual. I metodi virtual hanno una implementazione che puó esser sovrascritta oppure utilizzata cosí com'é dalle classi derivate. I metodi pure virtual non hanno una propria implementazioni e vengono seguite da un = 0: in questo caso, le classi non possono avere una loro istanza, ma vanno obbligatoriamente derivate e i loro metodi vanno definiti.
Riferendoci all'esempio, non posso creare un oggetto Veicolo, ma devo per forza creare un oggetto Aereo oppure Sommergibile, in cui definisco come questi oggetti possono accelerare o frenare.

Ecco come possiamo trasformare questi concetti in C++:


//Vehicles.h

#ifndef VEHICLES_H
#define VEHICLES_H

//Definisco il tipo di motore
enum class EngineType { Undefined, Electric, Termic };

//Definisco il tipo di Vehicle
enum class VehicleType { Undefined, Terrestrial, Naval, Aerial };

class Vehicle
{
  public:
    virtual ~Vehicle(){} //distruttore

    virtual EngineType getEngineType() const final
    {
       return m_engine;
    }

    virtual VehicleType getVehicleType() const final
    {
       return m_type;
    }

    virtual int getSpeed() const final
    {
       return m_speed;
    }

    virtual void accelerate() = 0;

    virtual void decelerate() = 0;

  protected:
    int m_speed{0};
    EngineType m_engine{EngineType::Undefined};
    VehicleType m_type{VehicleType::Undefined};
};

class VehicleTerrestrial : public Vehicle
{
  public:
    VehicleTerrestrial()
    {
        m_type = VehicleType::Terrestrial;
    }

    virtual ~VehicleTerrestrial(){}

    virtual int wheels() const final
    {
       return m_wheels;
    }

  protected:
    int m_wheels{0};
};

class VehicleNaval : public Vehicle
{
  public:
    VehicleNaval()
    {
        m_type = VehicleType::Naval;
    }

    virtual ~VehicleNaval(){}

    virtual bool hasBackupBoats() const final
    {
       return m_backup_boats;
    }

  protected:
    bool m_backup_boats{false};
};

class VehicleAerial : public Vehicle
{
  public:
    VehicleAerial()
    {
        m_type = VehicleType::Aerial;
    }
    virtual ~VehicleAerial(){}

    virtual bool hasStorageRoom() const final
    {
       return m_storage;
    }

  protected:
    bool m_storage{false};
};

#endif // VEHICLES_H


Finora abbiamo realizzato la "prima generazione" del nostro albero genealogico. Come potete osservare, non abbiamo un costruttore (le classi sono astratte, non possono essere creati oggetti di tipo Veicolo o VeicoloNavale) ma solo un distruttore virtuale e metodi virtuali e puramente virtuali.

Il distruttore e' virtuale perché ci permette di poter modificare gli oggetti di classi derivate da Veicolo attraverso i puntatori. Infatti, potremo creare una Automobile nel seguente modo:



Veicolo *automobile = new Automobile();
delete automobile;  //senza il virtual destructor non funzionerebbe!
std::unique_ptr m_veicolo(new Automobile());
std::vector veicoli;
Procediamo con la definizione di una automobile. Le implementazioni degli altri veicoli vengono lasciate a voi lettori come esercizio:

//Car.h

#ifndef CAR_H
#define CAR_H

#include "Vehicles.h"

class Car : public VehicleTerrestrial
{
  public:
    Car()
    {
        m_engine = EngineType::Termic;
    }

    virtual ~Car(){} //distruttore

    void accelerate() override
    {
      m_speed += m_accel; //definiamo una accelerazione lineare qui
    }

    void decelerate() override
    {
      m_speed -= m_accel;
    }

  private:
    int m_accel{20}; //20 m/s
    int m_doors{4};
    int m_seats{5};
};

#endif // CAR_H

Come potete vedere, posso accedere dalla mia classe Car (Automobile) al membro m_engine, che é protetto. Se esso fosse stato private, avrei potuto modificarlo solo tramite una funzione protected o public della classe base.

Trovate l'esempio nel mio repository su GitHub: https://github.com/blackibiza/ModernCpp che potete liberamente scaricare e modificare a vostro piacimento per sperimentare.

Alla prossima :)

Nessun commento:

Posta un commento