Membri static

Normalmente istanze diverse della stessa classe non condividono direttamente risorse di memoria, l'unica possibilita` sarebbe quella di avere puntatori che puntano allo stesso indirizzo, per il resto ogni istanza riceve nuova memoria per ogni attributo. Tuttavia in alcuni casi e` desiderabile che alcuni attributi fossero comuni a tutte le istanze; per utilizzare un termine tecnico, si vuole realizzare una comunicazione ad ambiente condiviso.
Per rendere un attributo comune a tutte le istanze occorre dichiararlo static:

  class MyClass {
    public:
      MyClass();
     /* ... */

    private:
      static int Counter;
      char * String;
      /* ... */
  };
Gli attributi static possono in pratica essere visti come elementi propri della classe, non dell'istanza. In questo senso non e` possibile inizializzare un attributo static tramite la lista di inizializzazione del costruttore, tutti i metodi (costruttore compreso) possono accedere sia in scrittura che in lettura all'attributo, ma non si puo` inizializzarlo tramite un costruttore:
  MyClass::MyClass() : Counter(0) { // Errore!
    /* ... */
  }
L'inizializzazione di un attributo static va eseguita fuori dalla classe, nel seguente modo:
  < MemberType > < ClassName >::< StaticMember > = < Value > ;
Nel caso dell'attributo Counter, si sarebbe dovuto scrivere:
    int MyClass::Counter = 0;
Successivamente l'accesso a un attributo static avviene come se fosse un normale attributo, in particolare l'idea guida dell'esempio era quella di contare le istanze di classe MyClass esistenti in un certo momento; i costruttori e il distruttore sarebbero stati quindi piu` o meno cosi`:
  MyClass::MyClass() : /* inizializzazione membri non static */ {
    ++Counter;
    /* ... */
  }

  MyClass::~MyClass() {
    --Counter;
    /* ... */
  }
Oltre ad attributi static e` possibile avere anche metodi static; la keyword static in questo caso vincola il metodo ad accedere solo agli attributi statici della classe, un accesso ad un attributo non static costituisce un errore:
  class MyClass {
    public:
      static int GetCounterValue();
      /* ... */

    private:
      static int Counter;
      /* ... */
  };

  int MyClass::Counter = 0;

  static int MyClass::GetCounterValue() {
    return Counter;
  }
Ci si puo` chiedere quale motivo ci possa essere per dichiarare un metodo static, ci sono essenzialmente tre motivi: Non e` possibile dichiarare static un costruttore o un distruttore.



Membri const

Oltre ad attributi di tipo static, e` possibile dichiarare un attributo const; in questo caso pero` l'attributo const non e` trattato come una costante: esso viene allocato per ogni istanza come un normale attributo, tuttavia il valore che esso assume per ogni istanza viene stabilito una volta per tutte all'atto della creazione dell'istanza stessa e non potra` mai cambiare durante la vita dell'oggetto. Il valore di un attributo const, infine, va settato tramite la lista di inizializzazione del costruttore:
  class MyClass {
    public:
      MyClass(int a, float b);
      /* ... */

    private:
      const int ConstMember;
      float AFloat;
  };

  MyClass::MyClass(int a, float b) : ConstMember(a), AFloat(b) { };
Il motivo per cui bisogna ricorrere alla lista di inizializzazione e` semplice: l'assegnamento e` una operazione proibita sulle costanti, l'operazione che si compie tramite la lista di inizializzazione e` invece concettualmente diversa (anche se per i tipi primitivi e` equivalente ad un assegnamento).
E` anche possibile avere funzioni membro const (in questo caso la keyword const va posta dopo la lista dei parametri del metodo) analogamente a quanto avviene per le funzioni membro statiche. Dichiarando un metodo const si stabilisce un contratto con il compilatore: la funzione membro si impegna a non accedere in scrittura ad un qualsiasi attributo della classe e il compilatore si impegna a segnalare con un errore ogni tentativo in tal senso. Oltre a cio` esiste un altro vantaggio a favore dei metodi const: sono gli unici a poter essere eseguiti su istanze costanti.
  class MyClass {
    public:
      MyClass(int a, float b) : ConstMember(a), AFloat(b) { };
      int GetConstMember() const {
        return ConstMember;
      }
      void ChangeFloat(float b) {
        AFloat = b;
      }

    private:
      const int ConstMember;
      float AFloat;
  };

  void main() {
    MyClass A(1, 5.3);
    const MyClass B(2, 3.2);
      
    A.GetConstMember();     // Ok!
    B.GetConstMember();     // Ok!
    A.ChangeFloat(1.2);     // Ok!
    B.ChangeFloat(1.7);     // Errore!
  }
Come per i metodi static, non e` possibile avere costruttori e distruttori const (sebbene essi vengano utilizzati per costruire e distruggere anche le istanze costanti).



Costanti vere dentro le classi

Poiche` gli attributi const altro non sono che attributi a sola lettura, ma che vanno inizializzati tramite lista di inizializzazione, e` chiaro che non e` possibile scrivere codice simile:
  class Bad {
    public:
      /* ... */

    private:
      const int Size;
      char String[Size];
  };
perche` non si puo` stabilire a tempo di compilazione il valore di Size. La soluzione al problema viene dalla keyword enum; se ricordate bene, e` possibile stabilire quali valori interi associare alle costanti che appaiono tra parentesi graffe al fine di rappresentarle.
Nel nostro caso dunque la soluzione e`:
  class Ok {
    public:
      /* ... */

    private:
      enum { Size = 20 };
      char String[Size];
  };
Chi ha fatto attenzione avra` notato che la keyword enum non e` seguita da un identificatore, ma direttamente dalla parentesi graffa; il motivo e` semplice: non ci interessava definire un tipo enumerato, ma disporre di una costante e quindi abbiamo creato una enumerazione anonima il cui unico effetto in questo caso e` quello di creare una associazione nome-valore all'interno della tabella dei simboli del compilatore.



Membri volatile

Il C++ e` un linguaggio adatto a qualsiasi tipo di applicazione, in particolare a quelle che per loro natura si devono interfacciare direttamente all'hardware. Una prova in tal proposito e` fornita dalla keyword volatile che posta davanti ad un identificatore di variabile comunica al compilatore che quella variabile puo` cambiare valore in modo asincrono rispetto al sistema:
  volatile int Var;

  /* ... */
  int B = Var;
  int C = Var;
  /* ... */
In tal modo il compilatore non ottimizza gli accessi a tale risorsa e ogni tentativo di lettura di quella variabile e` tradotto in una effettiva lettura della locazione di memoria corrispondente.
Gli oggetti volatile sono normalmente utilizzati per mappare registri di unita` di I/O all'interno del programma e per essi valgono le stesse regole viste per gli oggetti const; in particolare solo funzioni membro volatile possono essere utilizzate su oggetti volatile e non si possono dichiarare volatile costruttori e distruttori (che sono comunque utilizzabili sui tali oggetti).
Si noti che volatile non e` l'opposto di const: quest'ultima indica al compilatore che un oggetto non puo` essere modificato indipendentemente che sia trattato come una vera costante o una variabile a sola lettura, volatile invece dice che l'oggetto puo` cambiare valore al di fuori del controllo del sistema; quindi e` possibile avere oggetti const volatile. Ad esempio unita` di input, come la tastiera, sono solitamente mappati tramite oggetti dichiarati const volatile:
  const volatile char Byte;
  // Byte e` un oggetto a sola lettura il
  // cui stato varia in modo asincrono
  // rispetto al sistema



Dichiarazioni friend

In taluni casi e` desiderabile che una funzione non membro possa accedere direttamente ai membri (attributi e/o metodi) privati di una classe. Tipicamente questo accade quando si realizzano due o piu` classi, distinte tra loro, che devono cooperare per l'espletamento di un compito complessivo e si vogliono ottimizzare al massimo le prestazioni, oppure semplicemente quando si desidera eseguire l'overloading degli operatori ostream& operator<<(ostream& o, T& Obj) e istream& operator>>(istream& o, T& Obj) (T e` la classe cui appartengono i membri privati) per estendere le operazioni di I/O alla classe T. In situazioni di questo genere, una classe puo` dichiarare una certa funzione friend (amica) abilitandola ad accedere ai propri membri privati.
Il seguente esempio mostra come eseguire l'overloading dell'operatore di inserzione in modo da poter visualizzare il contenuto di una nuova classe:
  #include < iostream >

  class MyClass {
    public:
      /* ... */

    private:
      float F1, F2;
      char C
      void Func();
      /* ... */
     friend ostream& operator<<(ostream& o, MyClass& Obj);
  };

  void MyClass::Func() {
    /* ... */
  }

  // essendo stato dichiarato friend dentro MyClass, il seguente operatore
  // puo` accedere ai membri privati della classe come una qualunque
  // funzione membro.
  ostream& operator<<(ostream& o, MyClass& Obj) {
    o << Obj.F1 << ' ' << Obj.F2 << ' ' << Obj.C;
    return o;
  }
in tal modo diviene possibile scrivere:
  MyClass Object;
  /* ... */
  cout << Object;
L'esempio comunque risultera` meglio comprensibile quando parleremo di overloading degli operatori, per adesso e` sufficiente considerare ostream& operator<<(ostream& o, MyClass& Obj) alla stessa stregua di una qualsiasi funzione.
La keyword friend puo` essere applicata anche a un identificatore di classe, abilitando cosi` una classe intera:
    class MyClass {
        /* ... */
        friend class AnotherClass;
    };
in tal modo qualsiasi membro di AnotherClass puo` accedere ai dati privati di MyClass.
Si noti infine che deve essere la classe proprietaria dei membri privati a dichiarare una funzione (o una classe) friend e che non ha importanza la sezione (pubblica, protetta o privata) in cui tale dichiarazione e` fatta.


Pagina precedente - Pagina successiva



C++, una panoramica sul linguaggio - © Copyright 1997, Paolo Marotta