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

Derivazione. Approfondimenti

Il primo problema che ci poniamo è il seguente: tutte le funzioni membro di una classe lo sono anche di una sua derivata, a prescindere dalla loro accessibilità? Certamente, con delle eccezioni che vale davvero la pena di esaminare. Abbiamo detto che derivare una classe significa specializzarla; se una funzione membro della classe base non viene sovrapposta, significa che le ``novità'' introdotte nella classe derivata non la riguardano, per cui essa può essere ereditata senza alcun problema. Esistono tuttavia alcune funzioni membro che, per loro natura, devono necessariamente agire sulla struttura interna della classe, e non ha dunque senso che esse siano ereditate; si tratta, come si è già capito, dei costruttori, del distruttore e dell'operatore di assegnamento. Tali funzioni, nel caso non vengano definite, sono sintetizzate automaticamente dal compilatore; in quest'ultimo caso le chiamate ai costruttori o al distruttore della classe base avvengono automaticamente, come possiamo vedere nel seguente esempio:

// ex12_3_1.cpp
#include <iostream.h>
class A {
public:
  A () { cout << "A::A()\n"; }
  A& operator=(A&) { cout << "A::operator=()\n"; }
  ~A () { cout << "A::~A()\n"; }
};

class B : public A { };

void main() {
  B b, c;
  b = c;
}

output:


A::A()
A::A()
A::operator=()
A::~A()
A::~A()

Vediamo ora come costruire da noi costruttori, distruttore e operatore di assegnamento, tramite un semplice esempio. La prima classe che definiamo è la Array, sulla quale non ci soffermeremo affatto, avendo ormai visto sotto ogni possibile sfumatura luminosa le classi array.


class Array {
public:
  Array (unsigned n) {
    cout << "Array::Array()\n";
    nelem = n;
    array = new int[nelem];
    for (int i = 0; i < nelem; i++) array[i] = 0; }
  Array (const Array& a) {
    cout << "Array::Array(const Array&)\n";
    nelem = a.nelem;
    array = new int[nelem];
    for (int i = 0; i < nelem; i++) array[i] = a.array[i]; }
  const Array& operator= (const Array& a) {
    if (&a != this) {
      cout << "Array::operator= (const Array&)\n";
      if (nelem != a.nelem) {
	nelem = a.nelem;
	delete[] array;
	array = new int[nelem]; }
      for (int i = 0; i < nelem; i++)
        array[i] = a.array[i]; } }
  int& operator[] (int i) { return array[i]; }
  int somma() {
    int s = 0;
    for (int i = 0; i < nelem; i++) s+= array[i];
    return s; }
  ~Array () {
    cout << "Array::~Array()\n";
    delete[] array; }
private:
  int* array;
  unsigned nelem;
};

Si suppone ora di avere bisogno di un Array, il quale però abbia anche un nome immagazzinato in un char*; dobbiamo effettuare allora il subtyping della classe Array aggiungendo i campi necessari al nostro scopo. Siccome dobbiamo allocare un char* nello heap, dovremo programmare anche i costruttori, il distruttore e l'operatore di assegnamento.


class ArrayN : public Array {
public:
  ArrayN (unsigned n, char* s) : Array(n) {
    cout << "ArrayN::ArrayN()\n";
    nome = new char[strlen(s)];
    strcpy (nome, s); }
  ArrayN (const ArrayN& a) : Array(a) {
    cout << "ArrayN::ArrayN(const ArrayN&)\n";
    nome = new char[strlen(a.nome)];
    strcpy (nome, a.nome); }
  const ArrayN& operator= (const ArrayN& a) {
    cout << "ArrayN::operator=(const ArrayN&)\n";
    Array::operator= (a);
    if (strlen(nome) != strlen(a.nome)) {
      delete[] nome;
      nome = new char[strlen(a.nome)];
      strcpy (nome, a.nome); }
    return *this; }
  char* getNome () const { return nome; }
  void  setNome (char* s) {
    delete[] nome;
    nome = new char[strlen(s)];
    strcpy (nome, s); }
  operator char* () const { return getNome(); }
  ~ArrayN () {
    cout << "ArrayN::~ArrayN()\n";
    delete[] nome; }
private:
  char* nome;
};

Si faccia molta attenzione alla sintassi utilizzata per chiamare il costruttore di copia e l'operatore di assegnamento della classe base:


ArrayN (const ArrayN& a) : Array(a) { /*...*/ }
Nel costruttore di copia l'inizializzazione dell'oggetto ereditato avviene nella classica forma della initializer list, con la importante particolarità che l'oggetto a non è del tipo della classe base bensì di quella derivata. Per l'operator= la sintassi è:

Array::operator= (a);
Anche in questo caso l'oggetto a è di tipo ArrayN mentre l'operatore di assegnamento della classe base prevede come argomento un oggetto di tipo Array. Miracoli del C++.

Un semplice esempio che testi le classi or ora programmate è il seguente:


// ex12_3_2.cpp
#include <string.h>
#include <iostream.h>

/* definizioni di Array e ArrayN */

void main() {
  ArrayN a(5, "pippo");
  for (int i = 0; i < 5; i++) a[i] = i+1;
  ArrayN b(a);
  b.somma();    // 15
  b.setNome ("topolino");
  ArrayN c(a);
  c = b;
  c.getNome();  // topolino
}

output:
 Array::Array() a
 ArrayN::ArrayN() a
 Array::Array(const Array&) b
 ArrayN::ArrayN(const ArrayN&) b
 Array::Array(const Array&) c
 ArrayN::ArrayN(const ArrayN&) c
 ArrayN::operator=(const ArrayN&) c
 Array::operator= (const Array&) c
 ArrayN:: ArrayN() c
 Array:: Array() c
 ArrayN:: ArrayN() b
 Array:: Array() b
 ArrayN:: ArrayN() a
 Array:: Array() a



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