next up previous contents index
Next: Derivazione. Approfondimenti Up: Derivazione Previous: Derivazione   Indice   Indice analitico

Campi protetti

Il secondo possibile metodo di accesso alla implementazione delle classe base riguarda l'utilizzo in essa di campi protetti. Naturalmente è possibile dichiarare protected sia oggetti, di tipo classe o dei tipi primitivi, che funzioni. A scanso di equivoci deleteri, precisiamo che la derivazione protetta, che abbiamo accennato ad inizio sezione, è una dichiarazione ben differente dalla dichiarazione di un oggetto come protetto. Mentre la derivazione protetta è una operazione non troppo comune, dichiarare un campo come protetto è molto comune; infatti in tal caso, il campo in questione diventa accessibile da tutti i membri della classe derivata, come vedremo tra poco.

Continuando sulla scia della geometria piana, progettiamo le seguenti classi: Triangolo, Isoscele, Equilatero. Si suppone che tutte le classi vengano dichiarate nel file ex12_2_6.h, il quale includa anche i files di intestazione iostream.h e math.h.


class Triangolo {
public:
  Triangolo (double A, double B, double C)
    : a(A), b(B), c(C) {
    id = ++counter;
    cout << "Triangolo::Triangolo() " << id << "\n"; }
  double perimetro () const {
    return a + b + c; }
  double area () const {
    // utilizziamo la formula di Erone
    double p = perimetro() / 2;
    return sqrt(p * (p-a) * (p-b) * (p-c)); }
  ~Triangolo () { }
protected:
  double a, b, c;   // lunghezze dei lati del triangolo
  unsigned getId () { return id; }
private:
  static unsigned counter;
  unsigned id; // identificativo triangoli generici
};

La classe Triangolo contiene come campi privati un contatore statico, il quale ha la funzione di essere incrementato per ogni triangolo allocato, e un unsigned rappresentante un identificativo univoco del Triangolo. Essi sono private in quanto appartengono alla struttura interna della classe Triangolo. Anche le lunghezze dei lati appartengono concettualmente alla implementazione nascosta della classe, tuttavia si è preferito dichiararli protected in maniera tale che esse, una volta che la classe verrà specializzata tramite la derivazione, possano appartenere propriamente alla struttura interna delle classi derivate, senza utilizzate funzioni di accesso come quelle mostrate nella prima parte delle presente sezione. In generale il compromesso migliore consiste nell'utilizzare delle funzioni di accesso protette a campi interni privati, come mostrato per il campo id. Infatti, dichiarata in tale maniera la classe Isoscele:


class Isoscele : public Triangolo {
public:
  // `a' rappresenta la base
  // `b' rappresenta il lato obliquo
  Isoscele (double a, double b) : Triangolo(a, b, b) {
    // un triangolo isoscele ha lo stesso id
    // del triangolo generico dal quale deriva
    // il seguente statement e` ERRATO in quanto
    // il campo `unsigned id' e` private
    // id = Triangolo::id;
    // e` invece corretto utilizzare la funzione protected
    // `unsigned getId()': Isoscele deriva da Triangolo
    id = Triangolo::getId();
    cout << "Isoscele::Isoscele() " << id << "\n"; }
  double altezza () const {
    double semibase = Triangolo::a / 2;
    return sqrt(pow(b, 2) - pow(semibase, 2)); }
  double area () const {
    return a * altezza() * .5; }
  void setBase (double b) {
    Triangolo::a = b; /* OK: `a' e` un campo protetto */ }
  void setAltezza (double h) {
    double semibase = Triangolo::a / 2;
    Triangolo::b =
      Triangolo::c =
      sqrt(pow(h, 2) + pow(semibase, 2)); }
  ~Isoscele () { }
protected:
  unsigned getId () { return id; }
private:
  unsigned id; // identificativo triangolo isoscele
};
nel caso si voglia modificare l'implementazione della classe Triangolo sarebbe necessario aggiornare anche la Isoscele, il che è contrario allo spirito del C++, che quanto possibile inibisce la possibilità di dovere affrontare un aggiornamento ``a catena'' del codice scritto.

Come si vede la funzione getId() e i campi dati a, b, c vengono utilizzati pubblicamente all'interno della Isoscele. Addirittura non è neanche richiesto l'utilizzo dell'operatore ::, ove non esistano ambiguità. Per cui un equivalente sintattico della setBase() è semplicemente:


a = b;
ove, naturalmente, con b si intende l'argomento della funzione stessa, mentre se avessimo voluto accedere il campo dati della classe base avremmo dovuto forzatamente utilizzare Triangolo::b. In nessun caso nell'esempio precedente sarebbe stato necessario utilizzare il prefisso Triangolo::, che abbiamo aggiunto per chiarezza.

L'ultima classe è la seguente:


class Equilatero : public Isoscele {
public:
  Equilatero (double x) : Isoscele (x, x) {
    id = Isoscele::getId();
    cout << "Equilatero::Equilatero() " << id << "\n"; }
  double perimetro() const {
    return Triangolo::a * 3; }
  ~Equilatero () { }
protected:
private:
  unsigned id; // identificativo triangolo equilatero
};
la quale ci mostra come la qualifica di membro protected non vada ``perso'' con derivazioni successive; il compilatore ci ha infatti permesso di accedere il campo a della classe Triangolo; anche in questo caso il prefisso Triangolo:: è opzionale. Si noti invece che, per chiarezza, è sempre una buona idea specificare la classe di appartenenza di funzioni che siano state sovrapposte in precedenti derivazioni, come nel caso della getId() nel costruttore di Equilatero. Nel caso non venga specificato alcunché, e se naturalmente la funzione non viene sovrapposta nella classe specifica, il compilatore utilizza la funzione sovrapposta nella classe più ``vicina''; nel nostro caso avrebbe utilizzato la Isoscele::getId(), che è proprio quella voluta. Si tenga presente che può non essere immediato stabilire lo schema di derivazione, con relative sovrapposizioni, per progetti anche leggermente articolati.

Un esempio che ci mostri il funzionamento delle classi appena definite è il seguente:


// ex12_2_6.cpp
#include "ex12_2_6.h"

unsigned Triangolo::counter = 0;

void main() {
  Triangolo t(3, 4, 5);
  Isoscele i1(6, 5);
  Isoscele i2(0, 0);
  i2.setBase(16);
  i2.setAltezza(6);
  Equilatero e(10);
  cout << "triangolo generico(a=3,b=4,c=5):\n"
       << "\t2p = " << t.perimetro() << "\n"
       << "\tA  = " << t.area() << "\n"
       << "triangolo isoscele(base=6,obliquo=5):\n"
       << "\t2p = " << i1.perimetro() << "\n"
       << "\tA  = " << i1.area() << "\n"
       << "triangolo isoscele(base=16,altezza=6):\n"
       << "\t2p = " << i2.perimetro() << "\n"
       << "\tA  = " << i2.area() << "\n"
       << "triangolo equilatero(lato=10):\n"
       << "\t2p = " << e.perimetro() << "\n"
       << "\tA  = " << e.area() << "\n";
  // e.a = 5;  // NO: "member `a' is protected"
}

output:


Triangolo::Triangolo() 1
Triangolo::Triangolo() 2
Isoscele::Isoscele() 2
Triangolo::Triangolo() 3
Isoscele::Isoscele() 3
Triangolo::Triangolo() 4
Isoscele::Isoscele() 4
Equilatero::Equilatero() 4
triangolo generico(a=3,b=4,c=5):
        2p = 12
        A  = 6
triangolo isoscele(base=6,obliquo=5):
        2p = 16
        A  = 12
triangolo isoscele(base=16,altezza=6):
        2p = 36
        A  = 48
triangolo equilatero(lato=10):
        2p = 30
        A  = 43.3013
ex-2
si cerchi di spiegare come mai alcuni statement nel seguente esempio non sono corretti, e si immagini quale errore il compilatore possa lamentare:

// ex12_2_7.cpp
class Base {
public:
  int f() { return 1; }
protected:
  int g() { return 2; }
private:
  int h() { return 4; }
};

class Derivata : private Base {
public:
  int F() { return 8; }
protected:
  int G() { return 16; }
private:
  int H() { return 32; }
};

class Derivata2 : public Base {
public:
  int z() {
    // return f() + g() + h();  // NO
    return f() + g(); 
  }
protected:
private:
};

void main() {
  Derivata d;
  d.F();
  // d.G();  // NO
  // d.H();  // NO
  // d.f();  // NO
  // d.g();  // NO
  // d.h();  // NO
  Derivata2 d2;
  d2.f();
  // d2.g();  // NO
  // d2.h();  // NO
  d2.z();
}
ex-3
si spieghi come mai, nell'esempio sotto riportato, le righe commentate sono errate e, soprattutto, perché invece quelle non commentate sono lecite:

// ex12_2_8.cpp
class Nonno {
public:
  int f() { return 1; }
};

class Padre : public Nonno {
public:
  int g() { return f(); }
};

class Figlio : Padre {
public:
  int h() { return g(); }
};

void main() {
  Figlio f;
  // f.f();  // NO
  // f.g();  // NO
  f.h();
  Padre p;
  p.f();
  p.g();
}


next up previous contents index
Next: Derivazione. Approfondimenti Up: Derivazione Previous: Derivazione   Indice   Indice analitico
Claudio Cicconetti
2000-09-06