Newsgroup: it.comp.lang.c++
Titolo: Unica istanza

Considerate il seg. codice:
 >>>
#include <iostream>
using namespace std;
class Uovo
{
   static Uovo u;
   int i;
   Uovo(int ii) : i(ii) {}
   Uovo(const Uovo&); // previene la copy-costruzione
public:
   static Uovo* istanza() { return &u; }
   int val() const { return i; }
};
Uovo Uovo::u(47);
int main()
{
   // Uovo x(1);		// Errore!
   cout << Uovo::istanza()->val() << endl;
}
<<<<
Non è pazzesco? L'ho capito e allo stesso tempo mi gira la testa.
Kiuhnm
P.S. studio c++ da soli 3 giorni, ma mi pare che il libro "Thinking in 
C++" sia un po' machiavellico, a volte.

 C++" sia un po' machiavellico, a volte.
Non saprei. Per me "machiavellico" significa "astuto/subdolo/senza
scrupoli". Non mi č chiaro che cosa tu intenda.
Max

 scrupoli". Non mi è chiaro che cosa tu intenda.
Mi riferisco al termine originale /non/ dispregiativo e cioè sinonimo di 
sottile e intrigante.
Kiuhnm

 
provo a dire la mia, corregetemi se sbaglio.
La classe Uovo contiene un membro static Uovo.
Quindi anche se non crei una variabile Uovo nel main, esiste la variabile
membro dichiarata static (che fa uso di un costruttore senza argomenti
fornito dal compilatore, visto che non ne hai definito uno cosi')
prova a mettere un costruttore senza argomenti del tipo
Uovo() { cout << "Costruttore" << endl; }
e vedi che succede.
ciao

 e vedi che succede.
Non succede niente.
Quello che credo è che 'u' esista fin dalla prima riga del programma (a 
differenza di 'i' che è indefinita) poiché, essendo statica, è 
praticamente una variabile globale con lo scoping limitato alla classe 
Uovo. Il fatto che 'u' sia statica, evita inoltre che il costruttore sia 
chiamato all'infinito. Il debugger conferma l'ipotesi, ma mi pare 
comunque una feature un po' "oscura" perché veramente poco intuitiva.
Kiuhnm

 Quando viene creata?
L'unico oggetto è 'Uovo::u', che è un membro static, ed è creato prima 
dell'esecuzione del 'main'. 'Uovo::istanza' è una funzione static e non 
richiede alcun oggetto per essere invocata. (Le funzioni membro static sono 
grosso modo delle funzioni ordinarie che risiedono nello scope della 
classe.)
Max

 classe.)
Per es. ho anche trovato
int &h()
{
   static int x;
   return x;
}
ma mi pare bruttina come cosa.
Intuitivamente mi verrebbe da pensare a una variabile che esiste solo 
all'interno della funzione h, ma che è sempre la stessa.
Capisco che un compilatore la tratti come globale (con scope ridotto), 
ma idealmente non è globale. Per es. potrei pensare che un compilatore 
potrebbe voler deallocare x qualora fosse certo che nessun'altra 
funzione f fosse chiamata. Per es. potrebbe voler comprimere i dati in 
run-time associati a tutte le variabili statiche associate a funzioni 
statiche non chiamate da moltissimo tempo.
Comunque basta saperlo.
Kiuhnm

 Non succede niente.
Allora. Ho omesso di dire (mi sembrava ovvio) che devi sostituire
la riga
Uovo Uovo::u(47);
con
Uovo Uovo::u;
cosi' da riservare memoria chiamando il costruttore standard.
Per il resto non c'e' niente di oscuro ... ;-) oltre alle tue intenzioni ...
ciao

 Per il resto non c'e' niente di oscuro ... ;-) oltre alle tue intenzioni ...
Tenendo presente che static tratta le variabili locali come globali, ma 
a scope limitato, tutto è semplice. Poco intuitivo, ma semplice.
Kiuhnm

 scrupoli". Non mi è chiaro che cosa tu intenda.
Ecco un esempio.
Nel capitolo 11, l'autore spiega perché e come viene usato il 
costruttore di copia. Per fare ciò parla anche di assembly e di function 
frame, spiegando che le funzioni devono per forza restituire i valori 
attraverso i registri per problemi di ri-entranza causati da interrupt.
Il C++, per restituire dati "grandi", esegue quindi una copia nell'heap 
e ne restituisce l'indirizzo.
Qualcuno potrebbe pensare che è bene che un libro fornisca anche le 
motivazioni delle scelte, però dovrebbero essere corrette.
1) Non si tratta di ri-entranza. Si parla di ri-entranza quando 
*indirettamente* una funzione richiama sé stessa involontariamente e a 
sua insaputa (per es. in DOS non si potevano chiamare tutti i servizi 
DOS dall'interno dell'interrupt handler).
Se un processo esterno (o un interrupt) modifica i dati usati dallo 
stesso o da un altro processo, si avranno problemi, ma non si parla di 
ri-entranza.
2) Non è vero che non si possono restituire i dati sullo stack. Secondo 
l'autore si può farlo solo riservando spazio tra i parametri. Questo è 
completamente falso e qualunque programmatore di assembly appena 
competente può confermarlo (se non ti fidi di me). Ovviamente tenendo 
conto del problema dell'interrupt che può avvenire in qualunque momento.
Probabilmente l'autore non sa che esistono jmp o non ha mai pensato che 
è possibile duplicare l'indirizzo di ritorno.
Il libro è molto buono, ma a volte l'autore parla di cose che non 
conosce a fondo volendo dare a tutti i costi dettagli di cui il lettore 
potrebbe inoltre tranquillamente fare a meno.
In questo caso il libro può essere pericoloso, IMHO.
Kiuhnm

 Non č pazzesco? L'ho capito e allo stesso tempo mi gira la testa.
E` solo una delle possibili implementazioni in C++ del design pattern 
singleton. Detto questo non c'e` niente di pazzesco se ne hai capito il 
funzionamento.
[CUT]
Ciao,
Cesare

 In questo caso il libro può essere pericoloso, IMHO.
L'autore in quel capitolo ha solo spiegato cosa i compilatori C++ 
implementano e partendo da questo ha spiegato il perche` e` stato 
necessario introdurre il costruttore di copia.
Rileggilo meglio e vedrai che riuscirai a capirlo.
Tra l'altro il costruttore di copia e l'operatore assegnamento sono i 
classici pitfall in cui cade il programmatore C++ principiante, ecco 
perche` l'autore ne da` tanta enfasi.
Purtroppo il C++ e` complesso proprio perche` i compilatori fanno delle 
"cose" implicitamente, senza che il programmatore ne abbia coscienza.
Ecco perche` i programmatori C++ devono sapere *cosa* il compilatore 
sta` facendo a sua insaputa.
In C++ quando passi qualcosa per valore, quando incrementi qualcosa, 
quando assegni qualcosa succedono cose non scritte nel codice.
Cose che riguardano il costruttore di copia, l'operatore assegnamento 
che se non sono implementati vengono sintetizzati dal compilatore (prima 
cosa implicita) e vengono pure chiamati quando assegni o incrementi o 
passi per valore (altre sottigliezze implicite).
Ecco perche` queste "implicitezze" devono essere capite e messe sotto 
controllo.
Ciao,
Cesare

 controllo.
Potrei parlarti del Sole e del perché deve essere così caldo, ma forse 
ciò potrebbe sembrare fuori tema. Allo stesso modo non capisco come cose 
irrilevanti, per quanto concerne le mie obiezioni, dovrebbero 
contribuire alla discussione.
Kiuhnm

 premesse false (qualcuno le chiama giustamente "forzature").
Io non distorco niente.
Ho solo detto che l'autore fa` delle ipotesi sulle soluzioni possibili e 
dice quale e` stata scelta tra le soluzioni.
Non fa` premesse, non dice falsita`.
Tra l'altro la soluzione e` stata scelta dai designer dei compilatori e 
dagli enti di standardizzazione e non da lui.
[CUT]
Ciao,
Cesare

 ri-entranza.
Sbagliato. La "rientranza", come tu la chiami, è una proprietà del 
codice, e consiste nel non combinare casini con il multitasking. In 
pratica una funzione è detta rientrante se quando è chiamata da un 
processo, interrotta da un task switching e richiamata da un altro 
processo, non manda tutto a puttane :-)
Per fare questo deve soddisfare un certo numero di proprietà, fra cui 
quella di non accedere a dati condivisi con altre routine o processi, e 
nemmeno a variabili globali. Il sistema più sempice ed efficace è di 
usare solo lo stack e i registri per le variabili locali e i parametri 
di chiamata. Restituire i dati sullo stack? E' quello che fanno tutte le 
funzioni di questo mondo, appunto riservando spazio fra i parametri :-)
Riservare spazio extra sullo stack per passare "dati grandi" è una 
PESSIMA idea: la memoria riservata allo stack è molto poca comparata 
allo heap, e se finisce, il programma si blocca senza speranza. Invece 
con lo heap si può sempre allocare altra memoria.

 dagli enti di standardizzazione e non da lui.
Pensala come vuoi. E' inutile che ti ripeta quello che ho già scritto.
Kiuhnm

 con lo heap si può sempre allocare altra memoria.
Non ho mai detto che è una buona idea, ma solo che è possibile, al 
contrario di quanto dice l'autore, farlo /senza/ riservare spazio tra i 
parametri a priori.
Kiuhnm

 esecuzione", cioè che fossero tutte "uscite".
Non e' l'unico caso, dimentichi per l'appunto la ricorsione.
Ciao,
Nicola

 ricorsive costituiscono un caso particolare delle funzioni rientranti.
Questa è ignoranza bella e buona. Una funzione può essere ricorsiva e 
non ri-entrante.
Esercizio: fornire l'esempio di una funzione ricorsiva non ri-entrante.
Devo smettere di perdere tempo a scrivere lunghi messaggi esplicativi 
quando chi legge non ha alcuna intenzione di capirli. E' tempo sprecato.
Kiuhnm


Secondo me non hai le idee chiare ed i tuoi lunghi messaggi sono poco
comprensibili. Resto convinto che una funzione ricorsiva debba essere
rientrante. Fai tu l'esempio del contrario oppure taci.
Nicola

 rientrante. Fai tu l'esempio del contrario oppure taci.
int f(int a)
{
   static int g = 1;
   int tmp = g;
   g /= g;
   g = 0;
   tmp++;
   g = tmp;
   if (g < a)
     return f(a)+1;
   return g;
}
Kiuhnm

 }
è rientrante :-)

 è rientrante :-)
No, non lo è e si dim. in 3 sec., ma vorrei che ci arrivassi da solo.
Ti ricordo che la rientranza è
"la proprietà di un modulo di un software che assicura che tale modulo 
possa essere chiamato simultaneamente da più di un'unità di esecuzione 
concorrente."
Kiuhnm

 è rientrante :-)
No, non e' rientrante. Dal momento che fa uso di una variabile statica
che viene modificata all'interno della funzione e' evidente che la sua
esecuzione concorrente nello stesso spazio di indirizzamento puo'
portare a risultati imprevedibili. Questo non causa problemi nella
ricorsione perche' il passo di ricorsione e' tenuto ben separato dalla
modifica della variabile statica.
Sussman e Abelson, e non solo loro, obietterebbero che non e' nemmeno
ricorsiva ;-)
Ciao,
Nicola

 ricorsiva ;-)
Non confondiamo /funzione/ ricorsiva con /processo/ ricorsivo.
Se non ricordo male anche A&S fanno questa distinzione. Qualcuno 
chiamerebbe la mia funzione tail-recursive, ma ogni funzione 
tail-recursive è ricorsiva, per def.
Volevo solo mantenere l'esempio il più semplice possibile.
Kiuhnm


D'accordo, hai ragione sui termini. La rientranza e' una condizione
sufficiente per la ricorsione, ma non necessaria.
Ciao,
Nicola

Nicola Musatti ha scritto:

negli os che supportano il multithreading e' stato aggiunto il TLS
proprio per poter avere variabili statiche all'interno della funzione
distinte per ciascun thread. Es. in win32 occorre avviare il thread con
_beginthread() anziche' con CreateThread(). Diciamo che e' stata una
pezza per alcune funzioni della libreria del C che usano variabili
statiche, es. strtok

 statiche, es. strtok
E ancora non basta. Pensa a cosa accade se la funzione viene chiamata 
all'interno di un interrupt.
Kiuhnm