Newsgroup: it.comp.lang.c++
Titolo: singleton più efficienti con i reference

In un programma di grosse dimensioni, in cui (purtroppo!) si fa largamente
uso di un oggetto globale "gl" di una data classe che chiameremo Singleton,
ho avuto la necessità di sostituire tale oggetto con uno di una sottoclasse,
che chiameremo Subsingleton.
Non volendo sostituire manualmente tutti i riferimenti all'oggetto globale
con l'invocazione di un oppportuno metodo Instance() ho optato per la
seguente soluzione:
Nell'header file della classe Singleton ho sostituito la dichiarazione
    extern Singleton gl;
con la dichiarazione
    extern Singleton& gl;
Mentre nel file di implementazione ho sostituito la definizione
    Singleton gl;
con la definizione
    Singleton& gl =
#ifdef _SUB_SINGL_
            * (new SubSingleton);
#else
            * (new Singleton);
#endif
Mi sembra che quest'implementazione del pattern sia più efficiente di quella
tradizionale, che comporta l'overhead di chiamata alla funzione Instance per
accedere all'oggetto, in aggiunta all'overhead dovuto all'indirezione creata
dal puntatore, senza contare il test (instance_ == 0) che viene fatto ad
ogni richiesta.
D'altra parte l'implementazione col reference non può garantire l'unicità
dell'oggetto (in quanto l'allocazione non avviene all'interno di Instance
bensì in un punto esterno alla classe, e quindi il costruttore non può
essere protetto), e inoltre costringe ad allocare l'oggetto in ogni caso,
cioè non consente di ritardarne la costruzione fino al momento del primo
utilizzo.
A parte questi svantaggi (che nel mio caso specifico sono irrilevanti) non
mi sembra ci siano altri prolemi. Che ne pensate? E'davvero più efficiente
oppure è solo un'illusione?
°^°Walter

 oppure è solo un'illusione?
A me mi pare che così alla fine istanzi un oggetto di nome Singleton (ma
solo di nome!), in modo del tutto tradizionale, senza tener conto dei
vantaggi che appunto un Singleton vero ti può dare come ad esempio appunto
l'unicità dell'istanza evitando appunto la chiamata esplicita della new.
Ad ogni modo, se quello che vuoi ottenere è l'istanziazione di un oggetto di
tipo Classe oppure un oggetto di tipo SubClasse a seconda dei casi (ossia
della definizione di _SUB_SINGL_), quello che hai postato mi pare corretto,
ma non parlerei di maggiore efficienza: sarebbe come dire che
Classe a = new Classe() è più efficiente di Singleton s =
Singleton::GetInstance(), sono due cose diverse che hanno scopi diversi.
Ciao
Andrea


Se gl è definito come
    Singleton& gl = // qualcosa
allora il confronto giusto non è quello da te riportato, bensì il seguente:
    gl.metodo( ) è più efficiente di
    Singleton::Instance()->metodo()
perchè nel primo caso hai la chiamata in più alla funzione Instance, e
inoltre all'interno di tale funzione controlli ogni volta se l'istanza è
stata creata già o deve esserlo. Con la mia soluzione invece l'istanza viene
creata a compile time, e comunque viene creata una sola volta, non c'è
bisogno di fare new ogni volta (altrimenti che singleton sarebbe??).
Ciao,
Walter


Con la tua soluzione ti ritrovi un oggetto globale che và contro i principi
della programmazione ad oggetti ossia incapsulamento e information hiding,
proprio quello per cui è stato pensato il pattern Singleton.
Ciao
Andrea

 
mah... gira e rigira il Singleton è sempre un oggetto globale.
-- 
"Come un nano sull'orlo di un orinale dovevo muovermi in punta dei piedi"

 mah... gira e rigira il Singleton è sempre un oggetto globale.
E' un oggetto che definisce un'area di memoria globale STATICA, a cui si può
accedere in modo controllato.
Ti faccio un esempio (ma se ne potrebbero fare altri) di un semplice
vantaggio:
void funzione1() {
        Singleton *s = Singleton::GetInstance();
        s->metodo1();  //utilizza sempre l'oggetto unico anche se *s è
locale
}
Classe g; << definisco l'oggetto globale g da qualche parte
...
void funzione1() {
        Classe g; << definisco l'oggetto locale g
        g->metodo1();  << utilizza metodo1() di g locale, ma era quello che
volevamo? O volevamo ::g->metodo1()?
}
Ciao
Andrea

 Andrea
Evidentemente non ti è chiaro lo scopo del Singleton. Il Singleton E' UN
OGGETTO GLOBALE. I motivi per cui si preferisce usare Instance( ) piuttosto
che una variabile globale sono i seguenti:
    1) rendersi indipendenti dal "nome" dell'oggetto in modo da poter
sostituire "in modo trasparente" l'oggetto stesso con quello di una
sottoclasse; questo corrisponde ad INCAPSULAMENTO e INFORMATION HIDING, dal
momento che stai nascondendo all'utilizzatore quale classe sta     realmente
usando, se il Singleton o una sua sottoclasse.
    2) avere un'istanza unica
    3) ritardarne la creazione fino al momento della prima richiesta.
La soluzione da me proposta, che fa uso del reference, come ho già spiegato
in precedenza, consente la proprietà 1), che è la caratteristica più
importante, cioè l'intercambiabilità dell'oggetto, che sarebbe impossibile
utilizzando una vera variabile globale.
La proprietà 2) si può assicurare semplicemente usando direttamente
l'oggetto g, "senza crearlo" appositamente, in questo modo:
    g.metodo();   // l'unica istanza di g si trova in un file di
implementazione, ed è accessibile grazie alla
                                 dichiarazione extern;
La proprietà 3) non è importante, nel mio caso specifico.
Il singleton ottenuto con l'implementazione da me proposta soddisfa queste
proprietà (incapsulamento ed information hiding) senza l'overhead della
chiamata ad Instance. Se la cosa ancora non ti è chiara evidentemente non ti
è chiara la differenza tra una variabile usuale ed una di tipo reference...
Ciao,
Walter

Se la cosa ancora non ti è chiara evidentemente non ti è chiara la
differenza tra una variabile usuale ed una di tipo reference.
Non voglio convicerti di niente, non rispondo alle persone arroganti.

 proprietà.
Infatti il mio non è un oggetto globale, bensì un reference globale.
Comunque, anche una normale variabile globale realizzerebbe lo stesso
l'information hiding nel senso che dici tu, se non avesse attributi
pubblici. Non c'è differenza, in questo senso, tra:
    ogg.metodo( );
e
    Singleton::Instance( )->metodo;
in entrambi i casi stai invocando un metodo pubblico e non vedi la struttura
interna dell'oggetto.
Il punto è un altro: il vero significato di "information hiding" nel
contesto del pattern singleton, ha implicazioni ancora più forti, che non si
esauriscono nel nascondere la struttura interna dell'oggetto:
in questo contesto "information hiding" significa sostituire in maniera
trasparente l'implementazione del singleton, che può essere demandata a una
sottoclasse. Stai cioè nascondendo un dettaglio implementativo dietro un
nome unico.
Chiedo scusa per l' "arroganza", ma mi pare che tu abbia liquidato troppo in
fretta le mie considerazioni su questa implementazione:
    quando dici "un oggetto globale [...] secondo me va contro queste
proprietà"
inanzitutto non ti riferisci alla soluzione da me proposta, che utilizza un
"reference" globale (in pratica un puntatore) e non un "oggetto" globale;
inoltre stai esprimendo un'opinione senza motivarla (..."secondo me"...),
tirando in ballo dei principi generali (di per sè validi) ma senza
verificare se effettivamente siano violati o meno nella soluzione da me
proposta.
Mi dispiace per il tono polemico, ma credo sia giusto dimostrare
oggettivamente le proprie argomentazioni, e non limitarsi ai "secondo me".
Ciao,
Walter

"reference" globale (in pratica un puntatore) e non un "oggetto" globale;
No, mi riferivo anche alla tua soluzione, probabilmente non ho spiegato bene
cosa intendevo...
//file1.cpp
Classe &g = * (new Classe); << &g puntatore globale alla classe
....
int main {
 ...
}
//file2.ccp
void AltraClasse1::Foo()
{
    Classe g;
    g.metodo1(); // utilizza metodo1() di g locale, ma era quello che
volevamo? O volevamo ::g.metodo1()? (Nel post precedente ho dimenticato
qualche *!)
}
void AltraClasse::Foo2()
{
    ::g.SetAttribute(2);
    ::g   = * (new Classe);
    int att = ::g.GetAttribute(); //att non vale più 2, l'istanza non è
veramente "unica"
}
Foo2() mi sembra un'osservazione molto interessante (che dovrebbe esplicare
cosa intendo io, in questo specifico caso, per information hiding). Ossia
niente vieta a AltraClasse di richiamare la new su g. Il vecchio oggetto
viene perso (g non ha il controllo completo sulla istanza, il costruttore
viene richiamato e si ha un possibile memory leak). L'attributo att (che in
questo caso sarebbe la zona di memoria allocata da g), non vale più 2 dopo
la new (nonostante &g sia rimasta la stessa, la zona puntata è stata
riallocata).
Problema che non dovrebbe presentarsi con i Singleton "veri".
Ciao
Andrea


Certamente, ma io ho chiarito fin dal primo post che l'unicità dell'oggetto
non poteva essere "garantita" con la mia soluzione; ho messo in luce da
subito questo limite.
Il punto è il seguente: nel progetto a cui lavoro tutti i programmatori
coinvolti sanno benissimo che quella data classe è un singleton, e nessuno
si sognerebbe mai di crearne un'istanza locale di tale oggetto, nè tantomeno
di effettuare un assegnamento come nell'esempio da te riportato. Quindi
l'unicità dell'oggetto è garantita "per convenzione". Quello che nel mio
caso è molto più importante è la sostituibilità trasparente della classe
Singleton con una sottoclasse, insieme ad un'attenzione verso le
performance. In quest'ottica ripeto in altri termini quello che pensavo
fosse chiaro fin dal primo post:
- la soluzione classica con Instance è la più robusta e flessibile, ma
penalizza l'efficienza per due motivi:
    - l'overhead di chiamata alla funzione Instance
    - il controllo (instance_ == 0) ad ogni chiamata
- la soluzione da me proposta, col reference, rinuncia ad assicurare
l'unicità dell'oggetto, ma offre un vantaggio in termini
prestazionali.
In conclusione il messaggio era:
    se non avete bisogno a tutti i costi di dichiarare il costruttore
protetto e siete ragionevolmente sicuri che nessuno creerà istanze locali o
farà cose strane col singleton (tipo assegnargli qualcosa, come nel tuo
esempio) allora si può pensare ad una soluzione più efficiente anche se un
po' meno robusta.
Poi è chiaro che se qualcuno vuole a tutti i costi fare cose strane ci
riuscirà anche con Instance e con tutte le protezioni di questo mondo.
Basta che fai
    Singleton *p = Singleton::Instance()
dopodichè ti metti a giocare allegramente col puntatore p, incrementandolo a
caso e sporcando tutta la memoria che vuoi.
Le protezioni servono in alcuni contesti e sono del tutto inutili in altri:
nel mio caso specifico, ripeto (e non credo solo nel mio), l'unicità e la
protezione dell'oggetto globale non cositutiscono un problema, e ciò che mi
interessa davvero è la sostituibilità con la sottoclasse.
Ciao,
Walter


L'ordine di creazione degli oggetti allocati staticamente non è
determinabile a priori. Con la tua implementazione potresti utilizzare gl
*prima* che sia stato inizializzato (ovviamente puo' succedere solo nel
costruttore di un altro oggetto statico, presente in un'altra unita di
compilazione)
Comunque, per quanto riguarda l'efficienza, generalmente la "instance()"
può' essere inline e quindi trascurabile, test compreso, rispetto alla
chiamata delle funzioni membro di Singleton.
Supponendo che la classica Singleton::instance() sia inline, il costo e'
circa:
- caricamento del puntatore
- test (supponiamo positivo: il singleton esiste)
- puntatore disponibile per l'uso
Soluzione con reference:
- caricamento del puntatore
- puntatore disponibile per l'uso
Come vedi non cambia molto... Tenendo conto degli altri aspetti, consiglio
un vero Singleton.

 interessa davvero è la sostituibilità con la sottoclasse.
Riguardo alla sottoclasse (lasciando perdere l'efficienza),  si poteva fare
anche una cosa del genere (è solo una delle tante possibili soluzioni e
serve solo per far capire il concetto):
 template <typename T>
class Singleton
{
public:
     static T* GetInstance() {
         static T *pInstance;
         if (!pInstance)
               pInstance = new T;
         return pInstance;
   }
};
...
class Classe : public Singleton<Classe>{ ...
};
class SubClasse : public Classe {...
}
    Singleton* m_pSingleton =
#ifdef _SUB_SINGL_ //meglio ancora se questo venisse sostituito da
un'identificazione run-time del tipo e da un dynamic_cast
            SubClasse::GetInstance();
#else
            Classe::GetInstance();
#endif
Ciao,
Andrea


Eh no, putroppo questo non si può fare se vuoi garantire la sostituibilità
del Singleton con una sua sottoclasse; per dichiarare inline la Instance( )
devi porla nel .h. A questo punto, sempre nel .h, dovresti inserire anche il
.h della sottoclasse di Singleton che vuoi istanziare. Ciò è impossibile in
quanto si creerebbe una relazione circolare di inclusioni:
- l'header file della sottoclasse di Singleton deve necessariamente
includere l'header file della classe Singleton da cui deriva;
- l'header file di Singleton dovrebbe includere l'header file della sua
sottoclasse, per poterne creare un'istanza all'interno del metodo Instance
qualora questo sia definito inline nel .h;
Quindi purtroppo non hai scelta: se vuoi flessibilità devi dichiarare
Instance nel .cpp.
Inoltre il mio problema è che parto da un software in cui ci sono già
tantissimi riferimenti al nome della variabile globale sparsi nel codice;
non potevo sopportare di doverli sostituire tutti manualmente con
nstance( ), ma al tempo stesso mi serviva istanziare la sottolasse. Ecco
perchè ho dovuto utilizzare il reference.
Ciao,
Walter


Infatti è così che faccio normalmente. (Non capisco invece l'identificazione
run-time del tipo...: la questione è che devi istanziare un oggetto di una
classe da te stabilita, non ricavare il tipo di un oggetto già istanziato,
che è ciò che fai col dynamic cast)
Comunque il mio problema vero era un altro: io partivo da un software in cui
c'erano già
tantissimi riferimenti al nome della variabile globale sparsi nel codice;
non potevo sopportare di doverli sostituire tutti manualmente con
Instance( ), e d'altra parte mi serviva istanziare la sottoclasse. Ecco


Non capisco invece l'identificazione run-time del tipo...:
Ok, se il tipo è già noto a compile-time in effetti è inutile identificare
il tipo a run-time.


 perchè ho dovuto utilizzare il reference.
Ok... questo rende le cose difficili... in effetti era scritto nella tua
mail originale ma mi e' sfuggito...
al momento non ho sottomano ne un manuale ne un compilatore ma la butto li:
dato che l'operatore "." su un reference è a tutti gli effetti un operatore
"->", non funziona se ridefinisci l'operator -> per il singleton?


Forse non mi sono spiegato bene. In tutto il codice ci sono istruzioni del
tipo:
    single.operation( );
dove single è una variabile globale, di tipo Singleton. A questo punto ho
bisogno di sostituire Singleton con una sua sottoclasse, anch'essa dotata
del metodo "operation" che a questo punto sarà virtuale e ridefinito, ma
voglio fare ciò "senza cambiare tutte le istruzioni esistenti nel codice".
L'unico modo per farlo, quindi, (o almeno l'unico che ho trovato, ma non mi
pare ce ne siano altri) è dichiarare la variabile single di tipo Singleton&
e inizializzarla con un oggetto della sottoclasse.
Ciao,
Walter

 voglio fare ciò "senza cambiare tutte le istruzioni esistenti nel codice"
Sono io che non mi sono spiegato bene. Piu che una possibile soluzione, la
mia era una domanda.
Intendevo dire che, dato che i reference nascondono un puntatore:
   Singleton& x;
   x.operation()
E', piu' o meno,  trasformato dal compilatore in:
   Singlenton *const x;
   x->operation();   // o (*x).operation() ?
A questo punto mi chiedo, cosa succede se Singleton didefinisce operator->()
?
Il compilatore lo ignora o chiama la versione overload?
Probabilmente e' una stupidata (fanta-C++), ma nel secondo caso (ma non ho
mai provato), si potrebbe fare qualcosa tipo:
  class SingletonForwarder
  {
      public:
         Singleton& operator->()  //inline, costo zero (ottimizzato)
         {
            return *Singleton::instance();
         }
  };
SingletonForwarder & x;
x.operation(); //diventa (*SingletonBase::instance()).operation()  ?

 x.operation(); file://diventa (*SingletonBase::instance()).operation()  ?
L'operatore -> sottoposto ad overload viene invocato solo se chiamato
esplicitamente;
es.:
A oggetto;
oggetto->qualcosa;   // invoca l'operatore -> sovraccaricato per la classe A
Nel tuo esempio invece compare solo l'operatore "." e quindi il -> non può
mai essere invocato.
Walter