Esempio:
codice:
#include <iostream>
#include <stdexcept>
using namespace std;
// Una classe Vettore a dimensione fissata
template <typename T, unsigned int Size>
class Vettore
{
private:
T vec[Size];
/*
Versioni interne const e non-const
La versione const verrà usata:
- quando l'operatore [] viene usato sul lato destro di un assegnamento
- in generale quando viene richiamata la versione const dell'operatore []
La versione non-const verrà usata:
- quando l'istanza è non-const e l'operatore [] viene usato sul lato
sinistro di un assegnamento (se l'istanza è const teoricamente verrebbe
richiamata la versione const, ma ovviamente in tal caso l'assegnamento
non può aver luogo)
*/
const T& internalGetItem(unsigned int Pos) const
{
std::clog<<"internalGetItem const"<<std::endl;
// Qui implementa il codice per la versione const/rvalue
if(Pos>=Size)
throw std::out_of_range("Pos eccessivo.");
return vec[Pos];
}
T& internalGetItem(unsigned int Pos)
{
std::clog<<"internalGetItem non-const"<<std::endl;
// Qui implementa il codice per la versione non-const/lvalue
if(Pos>=Size)
throw std::out_of_range("Pos eccessivo.");
return vec[Pos];
}
public:
Vettore()
{
// Inizializzazione di default di tutti gli elementi del vettore interno
for(unsigned int i=0; i<Size; i++)
vec[i]=T();
}
// Classe proxy magica
class SubscriptProxy
{
private:
typedef Vettore<T, Size> ParentType;
typedef T ElemType;
// Classe genitore che andrà a richiamare per ottenere l'elemento
ParentType & parent;
// Posizione
unsigned int pos;
// Il costruttore non fa altro che inizializzare i campi
// Viene tenuto privato per evitare che dall'esterno si possa istanziare
SubscriptProxy(ParentType & Parent, unsigned int Pos) : parent(Parent), pos(Pos)
{};
public:
// Operatore di assegnamento (usato se la classe sta a sinistra di un
// assegnamento) - richiama la versione non-const ed effettua
// l'assegnamento tramite reference
ElemType& operator=(const ElemType & NewValue)
{
std::clog<<"SubscriptProxy::operator=, pos="<<pos<<std::endl;
return parent.internalGetItem(pos)=NewValue;
}
// Operatore di conversione implicita (usato se sta in un'espressione,
// non alla sinistra di un uguale)
operator const ElemType &() const
{
std::clog<<"SubscriptProxy::operator T, pos="<<pos<<std::endl;
// Forza l'uso della versione const trasformando parent in un
// riferimento const
const ParentType & constParent = parent;
return constParent.internalGetItem(pos);
}
// La classe genitore è friend così che la classe proxy possa accedere
// ai suoi membri privati
friend class Vettore<T, Size>;
};
// ... e viceversa (altrimenti la classe-genitore non potrebbe richiamare il
// suo costruttore
friend class Vettore<T, Size>::SubscriptProxy;
// Versione const dell'operatore [] - semplicemente chiama la versione const
// di internalGetItem
const T& operator[](unsigned int Pos) const
{
return internalGetItem(Pos);
}
// Versione non-const dell'operatore [] - restituisce una nuova istanza
// dell'oggetto-proxy, dicendole la posizione richiesta e dandole un
// riferimento all'istanza della classe padre a cui andrà poi a chiedere
// l'oggetto da restituire
SubscriptProxy operator[](unsigned int Pos)
{
return SubscriptProxy(*this, Pos);
}
};
// Prova con un riferimento costante (chiama la versione const di operator[])
void Test(const Vettore<int, 20> & Vec)
{
cout<<Vec[0]<<endl;
}
int main()
{
// Crea il nostro vettore
Vettore<int, 20> v;
// Assegnamento semplice
v[5]=10;
// Rilettura - nota che qui viene chiamato operator[] non-const, ma grazie
// alla classe proxy viene chiamata la versione const di internalGetItem
int i=v[5];
cout<<i<<endl;
// Assegnamento leggendo anche il valore restituito
cout<<(v[0]=20)<<endl;
// Rilettura (direttamente nel cout, l'operatore di conversione implicita
// viene comunque scelto in maniera corretta)
cout<<v[0]<<endl;
// Passo come riferimento costante
Test(v);
return 0;
}
Purtroppo un problema di questo approccio è che non è possibile fare cose come
codice:
v[0]++;
v[0]+=50;
e compagnia; non si possono neanche implementare gli operatori in questione manualmente nella classe proxy, dato che a quel punto la compilazione fallirebbe se il contenitore fosse usato su tipi che non implementano gli operatori su cui questi si basano.
Anche roba di questo genere
ha comportamento peculiare: non restituisce l'indirizzo dell'elemento in questione, ma dell'oggetto-proxy temporaneo; in effetti per ottenere l'indirizzo dell'elemento bisognerebbe fare
codice:
int * ptr = & static_cast<const int &>(v[0]);