next up previous contents index
Next: Derivazione Up: Classi derivate. Polimorfismo. Streams Previous: Classi derivate. Polimorfismo. Streams   Indice   Indice analitico

Composizione

Uno dei grandi vantaggi della programmazione tramite classi in C++ è che essa rende possibile il riutilizzo di codice in maniera semplice ed affidabile. Supponiamo di avere programmato una classe rappresentante un determinato tipo di oggetti, o che qualcuno lo abbia fatto per noi (in una libreria magari); possiamo utilizzare la nostra classe dichiarando uno o più oggetti di tale tipo all'interno di un'altra classe: tale tecnica, che abbiamo talvolta utilizzato nei nostri esempi, è nota con il termine di composizione. In effetti si tratta di programmare un certo numero di classi semplici e comporle in una più complessa e specifica; il nome stesso, ``composizione'', suggerisce l'operazione di assemblaggio: è come avere una portiera, una scocca, un motore e farne un'automobile. Si tratta di una procedimento molto efficace:

Consideriamo le seguenti classi:


// ex12_1_1.h
#include <iostream.h>
enum Colore { ARANCIO, GIALLO, VERDE };

class Passero {
  int peso;
public:
  Passero (int p) {
    peso = p;
    cout << "Passero::Passero()\n";  }
  ~Passero () {
    cout << "Passero::~Passero()\n"; }
};

class Canarino {
  Colore colore;
public:
  Canarino (Colore c) {
    colore = c;
    cout << "Canarino::Canarino()\n"; }
  ~Canarino () {
    cout << "Canarino::~Canarino()\n"; }
};

class Merlo {
  bool parlante;
public:
  Merlo (bool p) {
    parlante = p;
    cout << "Merlo::Merlo()\n"; }
  ~Merlo () {
    cout << "Merlo::~Merlo()\n"; }
};

Supponiamo che esse siano delle classi fondamentali per una classe specifica Voliera, composta con esse:


// ex12_1_1.cpp

#include <iostream.h>
#include "ex12_1_1.h"

class Voliera {
  Passero* passero;
  Canarino* canarino;
  Merlo* merlo;
public:
  Voliera (int peso,       // peso del passero
           Colore colore,  // colore del canarino
           bool parla) {   // attitudine del merlo
    cout << "Voliera::Voliera()\n";
    passero = new Passero(peso);
    canarino = new Canarino(colore);
    merlo = new Merlo(parla); }
  ~Voliera () {
    cout << "Voliera::~Voliera()\n";
    delete passero;
    delete canarino;
    delete merlo; }
};

void main() {
  Voliera voliera (5, GIALLO, true);
}

output:


Voliera::Voliera()
Passero::Passero()
Canarino::Canarino()
Merlo::Merlo()
Voliera::~Voliera()
Passero::~Passero()
Canarino::~Canarino()
Merlo::~Merlo()

All'interno della classe Voliera abbiamo dichiarato tre oggetti di tipo Passero, Canarino e Merlo tramite puntatori; l'inizializzazione di essi avviene nel costruttore e la loro deallocazione del distruttore ~Voliera(). Tuttavia, visto che gli oggetti contenuti nella classe Voliera appartengono ad essa, possiamo dichiarare anche dei veri e propri oggetti, come se si trattasse di oggetti di tipi primitivi. Dobbiamo però fare risolvere il seguente problema: se definiamo un oggetto esso necessita una chiamata al proprio costruttore, la quale deve avvenire prima della chiamata del costruttore della classe composta Voliera. La seguente implementazione è dunque errata:


class Voliera {
  Passero passero;
  Canarino canarino;
  Merlo merlo;
public:
  Voliera (int peso,       // peso del passero
           Colore colore,  // colore del canarino
           bool parla);    // attitudine del merlo
};
in quanto non possiamo in nessun modo ``anticipare'' la costruzione degli oggetti privati della classe Voliera. Esiste naturalmente la soluzione al nostro problema: per inizializzare un oggetto prima che il corpo del costruttore venga eseguito è sufficiente utilizzare la seguente sintassi:

ClasseComposta ([argomenti_del_costruttore])
  : Oggetto1([argomenti_oggetto_1]), 
    Oggetto2([argomenti_oggetto_2]),
    , ..., OggettoN([argomenti_oggetto_N]);
come vediamo nel seguente esempio:

// ex12_1_2.cpp

#include <iostream.h>
#include "ex12_1_1.h"

class Voliera {
  Passero passero;
  Canarino canarino;
  Merlo merlo;
public:
  Voliera (int peso,       // peso del passero
           Colore colore,  // colore del canarino
           bool parla)     // attitudine del merlo
  : passero(peso), canarino(colore), merlo(parla) {
    cout << "Voliera::Voliera()\n"; }
  ~Voliera () {
    cout << "Voliera::~Voliera()\n"; }
};

void main() {
  Voliera voliera (5, GIALLO, true);
}

output:


Passero::Passero()
Canarino::Canarino()
Merlo::Merlo()
Voliera::Voliera()
Voliera::~Voliera()
Merlo::~Merlo()
Canarino::~Canarino()
Passero::~Passero()

Dall'output del programma è evidente che la costruzione degli oggetti di tipo Passero, Canarino e Merlo avviene prima della chiamata al costruttore di Voliera. Si noti inoltre che la loro distruzione è automaticamente messa in atto dal compilatore dopo la distruzione dell'oggetto di tipo Voliera. Comporre classi è dunque una operazione del tutto sicura. Per analogia con i costruttori degli oggetti istanziati da classi programmate dall'utente, è possibile anche chiamare degli pseudo-costruttori per oggetti di tipo primitivo:


// ex12_1_3.cpp

#include <iostream.h>
#include <math.h>

class Prova {
  int n;
  double x;
  char* s;
public:
  Prova (int N, double X)
    : n(N), x(X), s(0) { }
  operator double() {
    return pow(x, n); }
};

void main() {
  Prova p(3, 4);
  cout << p << "\n";
}

output:


64

ex-2
si programmino due classi fondamentali, Abitacolo e Portabagagli, aventi un costruttore con argomento un numero intero rappresentante il loro volume; si definisca per entrambe l'operatore di conversione ad intero, che restituisce il valore dell'unico campo dati di esse contenente appunto il volume;
ex-3
si componga con esse la classe Automobile; si ridefinisca l'operatore di uscita sull'output standard, avente una sintassi del tipo:

abitacolo: XX litri
portabagagli: XX litri


next up previous contents index
Next: Derivazione Up: Classi derivate. Polimorfismo. Streams Previous: Classi derivate. Polimorfismo. Streams   Indice   Indice analitico
Claudio Cicconetti
2000-09-06