Membri const e mutable
Oltre ad attributi di tipo static , e` possibile avere attributi
const ; in questo caso pero` l'attributo const non
e` trattato come una normale 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), la cosa diverra`
piu` evidente quando vedremo che il generico membro di una classe puo` essere
a sua volta una istanza di una generica classe.
E` anche possibile avere funzioni membro const 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 (che per loro natura non possono essere
modificate). Per dichiarare una funzione membro const e` necessario far
seguire la lista dei parametri dalla keyword const , come mostrato nel
seguente esempio:
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;
};
int main(int, char* []) {
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!
return 0;
}
Si osservi che se la funzione membro GetConstMember() fosse stata
definita fuori dalla dichiarazione di classe, avremmo dovuto nuovamente esplicitare
le nostre intenzioni:
class MyClass {
public:
MyClass(int a, float b) : ConstMember(a),
AFloat(b) {};
int GetConstMember() const;
/* ... */
};
int MyClass::GetConstMember() const {
return ConstMember;
}
Avremmo dovuto cioe` esplicitare nuovamente il const (cosa che non avviene
con le funzioni membro static ).
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).
Talvolta puo` essere necessario che una funzione membro costante possa accedere
in scrittura ad uno o piu` attributi della classe, situazioni di questo genere
sono rare ma possibili (si pensi ad un oggetto che mappi un dispositivo che
debba trasmettere dati residenti in ROM attraverso una porta hardware, solo
metodi const possono accedere alla ROM...). Una soluzione potrebbe essere
quella di eseguire un cast per rimuovere la restrizione del const , ma
una soluzione di questo tipo sarebbe nascosta a chi usa la classe.
Per rendere esplicita una situazione di questo tipo e` stata introdotta la keyword
mutable , un attributo dichiarato mutable puo`
essere modificato anche da funzioni membro costanti:
class AccessCounter {
public:
AccessCounter();
const double GetPIValue() const;
const int GetAccessCount() const;
private:
const double PI;
mutable int Counter;
};
AccessCounter::AccessCounter() : PI(3.14159265),
Counter(0) {}
const double AccessCounter::GetPIValue() const {
++Counter; // Ok!
return PI;
}
const int AccessCounter::GetAccessCount() const {
return Counter;
}
L'esempio (giocattolo) mostra il caso di una classe che debba tenere traccia
del numero di accessi in lettura ai suoi dati, senza mutable e senza
ricorrere ad un cast esplicito la soluzione ad un problema simile sarebbe stata
piu` artificiosa e complicata.
|