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:
MyClass Obj; int Var1 = Obj.GetCounterValue(); // Ok! int Var2 = MyClass::GetCounterValue(); // Ancora Ok!
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).
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).
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.
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
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.
const volatile char Byte; // Byte e` un oggetto a sola lettura il // cui stato varia in modo asincrono // rispetto al sistema
#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
class MyClass { /* ... */ friend class AnotherClass; };in tal modo qualsiasi membro di AnotherClass puo` accedere ai dati privati di MyClass.