Newsgroup: it.comp.lang.c++
Titolo: smart pointers con ref_count

Sto pensando di utilizzare degli smart pointers implementati con 
ref_count. Ci sono soluzioni più efficienti? Come mai si parla tanto di 
garbage collector e non di questo sistema più semplice?
E.

 garbage collector e non di questo sistema più semplice?
Si e' parlato anche di questo, nel recente thread su GC.
Riassumendo la *mia* opinione: credo che siano piu' efficienti, ma solo 
nella maggioranza dei casi.
  bye
	av.

 garbage collector e non di questo sistema più semplice?
Non ho idea se siano piu' efficienti gli shared_ptr o la garbage 
collection, ma ho idea che:
1) Nel caso, non molto frequente, ma neanche molto raro, di riferimenti 
circolari, la garbage collection riesce a deallocare mentre gli 
shared_ptr no.
2) Gli auto_ptr sono piu' efficienti di entrambi, per quanto di 
applicabilita' piu' limitata. Insomma, se ti basta un auto_ptr usalo, 
altrimenti usa uno shared_ptr.
3) Comunque, per ottimizzare le prestazioni, e' meglio cercare di 
minimizzare allocazioni e disallocazioni nello heap. Insomma, cerca di 
non eseguire "new" dentro un loop di calcolo.
4) In alcune implementazioni gli shared_ptr sono particolarmente lenti 
se si compila con il flag di supporto al multithreading. Insomma, se non 
usi i thread, disattiva tale opzione.
5) Per evitare memory leak senza perdere efficienza, ogni volta che fai 
una new cerca di decidere chi sara' il proprietario di tale oggetto, 
cioe' il responsabile della sua delete. Se non riesci proprio a 
deciderlo in quel punto, usa uno smart_pointer.
-- 
     Carlo Milanesi
 
http://digilander.libero.it/carlmila

 nella maggioranza dei casi.
Come mai in Java e Python si è scelto di usare il GC invece degli smart 
pointers?
E.


Questo sistema non può essere utilizzato in modo efficiente se non da un 
essere umano, e inoltre richiede deallocazioni manuali che il GC evita.
Con il garbage collector si usano puntatori che mantengono in vita gli 
elementi. Mentre tramite ref_count si avrebbero incrementi e decrementi 
per ogni modifica di ogni singolo puntatore, il GC è molto meno 
conservativo e quindi più efficiente, nel caso generale. Per es., un 
puntatore che punti a un elemento x potrebbe variare 1000 volte, quindi 
diventare illegale e poi puntare nuovamente a x. Un GC potrebbe non 
accorgersi che il puntatore è diventato illegale, quindi il codice è da 
considerarsi errato. Questo è un problema molto brutto. Altro che leak...
D'altro canto, l'altro metodo elimina x (se non puntato da altri 
puntatori) non appena il puntatore diventa illegale, però al costo che 
ogni modifica del puntatore richiede un aggiornamento del contatore.
Per es. immagina cosa accade ogni volta che tramite un puntatore si 
percorre una lista. Considera inoltre che in caso di multi-threading, 
ogni incremento/decremento del contatore richiede il locking del bus con 
notevole degrado della performance generale di tutti i programmi in 
esecuzione (fra un po' i sistemi saranno tutti multiprocessore e 
maggiore è il numero di processori, più inefficiente è il locking).
Kiuhnm

 più inefficiente è il locking).
Vero, e in questo il GC guadagna punti.
Pero' non so come il thread GC debba comportarsi durante la scansione,
non se deve bloccare tutte le allocazioni e deallocazioni durante il
checksi rischia di guadagnarci ben poco.
Un'altro vantaggio del GC e' la deallocazione asincrona (e tutta in un
momento), che risolve un po' di problemi di efficienza degli
allocatori/deallocatori "standard". 
--
Marco
 
-- 
I'm using an evaluation license of nemo since 45 days.
You should really try it!
http://www.malcom-mac.com/nemo

 allocatori/deallocatori "standard".
Già.
Kiuhnm

 percorre una lista. 
Già, di questo devo tener conto.
La mia situazione e' quella di avere degli oggetti che contengono 
puntatori ad altri oggetti. A volte uno di questi oggetti viene 
cancellato, e gli oggetti che vi facevano riferimento devono 
accorgersene e mettere i propri puntatori a zero. Per questo un oggetto 
"cancellato" deve rimanere allocato finche' non sono sicuro che nessun 
altro oggetto vi faccia piu' riferimento.
La soluzione con i ref_count mi sembra molto naturale in questo 
contesto. Inoltre io posso mettere il ref_count direttamente negli 
oggetti puntati, senza bisogno di creare una doppia indirezione. Dunque 
l'unica penalizzazione ce l'ho quando faccio una copia di un ref_count, 
e non quando accendo agli elementi puntati.
Per ovviare al problema della lista (o casi simili), posso pensare di 
mettere a disposizione un metodo che restituisce il puntatore vero, tipo:
void updateList(SmartPtr<Object> list) {
    Object *p=list.ptr();
    for (;p;p=p->next)
      p->update();
}
ovviamente la funzione ptr() mina l'intera struttura degli SmartPtr, e 
forse sarebbe meglio non renderla affatto disponibile, oppure metterla 
protected.
Mi viene ora in mente che proprio riguardo a queste liste mi sono 
ritrovato in una situazione un po' particolare. Immagino che non sia una 
novita':
ho una lista di oggetti che possono essere aggiornati. Ho il metodo che 
aggiorna tutti gli oggetti scorrendo la lista, come updateList. Succede 
pero' che durante l'aggiornamento l'oggetto puo' decidere di togliersi 
dalla lista! La soluzione che io ho adottato e' stata quella di 
memorizzare l'elemento successivo prima di aggiornare l'oggetto:
for (p=first;p;) {
	Object *next=p->next;
	p->update();
	p=next;
}
Vi pare una porcheria o e' un modo "giusto" di procedere?
In effetti il problema si riproporrebbe se l'update di un oggetto 
potesse rimuovere dalla lista un altro oggetto (che per sfortuna 
potrebbe proprio essere il next!). Ma nel mio caso questo non succede 
perche' un oggetto puo' solo "chiedere" ad un altro di cancellarsi, non 
lo puo' fare lui direttamente. L'oggetto quindi si cancellera' solo 
durante il proprio update(). In qualche senso questo modo di procedere 
mi sembra piuttosto naturale...
E.

 mi sembra piuttosto naturale...
In generale, quanto dici è giusto. Un altro metodo consiste nel marcare 
  gli oggetti come "da cancellare" e quindi nel procedere periodicamente 
ad eliminare gli elementi da cancellare non puntati da ptr speciali, 
oppure nello "spostare" tali ptr quando necessario.
Kiuhnm

 è sempre la sincronizzazione.
Eddai, lock inc e lock dec non sono la fine del mondo, e su processori
multicore sono fatte per essere efficienti...
Se gia' nella lista stai cercando una stringa, il prezzo delle
operazioni atomiche e' ininfluente.
--*PaN!*


)
Allo stesso modo Wind. non ha mai utilizzato i task gate di Intel
(switching hw tra task) perché troppo lenti.
Quindi non sempre ciň che č hardware č piů veloce. La strada migliore č
quella di eliminare le funzioni di sincronizzazione quando non
richieste.
Kiuhnm

 pointers?
Vedo che usi il termine GC e 'smart pointer' in modo un poco improprio.
In CS uno smart-pointer è un 'puntatore' che utilizza features
aggiuntive rispetto ai puntatori 'grezzi'. Queste features possono
essere relative alla gestione automatica della memoria, garbage
collection 'in altro modo', bound checking e chi più ne ha più ne metta.
In particolare comunque uno smart pointer è un *tipo* di dato astratto.
Garbage Collection invece è una forma di gestione automatica della
memoria.
Ho parlato di 'improprio' perchè pare che tu stia usando il termine
'smart pointers' per indicare 'gestione della memoria tramite reference
counting'. In effetti un modo per gestire la memoria tramite reference
counting fa uso di smart pointers. Viceversa per un linguaggio che 'di
statuto' fa ref counting gli smart pointers potrebbero essere (o meno)
una possibile implementazione. Per esempio Objective C 1.x utilizza
reference counting sugli oggetti (manuale tra l'altro, nel senso che si
chiama 'a mano' +release, +autorelease o +retain sugli oggetti).
Tuttavia i puntatori sono puntatori 'scemi' e il reference counting è
tenuto dentro l'oggetto. Il vincolo è che dato un frammento di codice
che è sia C valido che Objective C valido (ovvero un qualunque frammento
di C) questo deve avere lo stesso comportamento usando come
interpretazione la semantica di C e di Objective C. Per cui i puntatori
devono mantenere il vecchio comportamento e idem gli assegnamenti.
BTW Objective-C 2.0 fa garbage collection (automatica, quindi).
A questo punto viene fuori il problema: un linguaggio 'di alto livello'
che quindi si mette la sua semantica ad assegnamento et similia potrebbe
precisamente fare garbage collection tramite reference counting. Questo
è precisamente quello che fa l'implementazione C di Python. Garbage
collection tramite reference counting.
Python *non* specifica l'algoritmo con cui bisogna fare garbage
collection (e questo è il motivo per cui non possiamo assumere
deterministica la distruzione degli oggetti in Python, anche se, in
pratica lo è quasi sempre -- p.s. questo non ha nulla a che vedere con
il costrutto with che invece rende la cosa *davvero*, *sempre* e per
*statuto* deterministica). Per cui C Python lo fa con ref counting (+ un
occasionale mark and sweep quando siamo idle per chiappare i loop).
D'altra parte per esempio IronPython (su .Net) utilizza gc standard di
.Net e per quanto ne so Jython quello della JVM.
L'unica cosa che il programmatore sa è che viene fatta garbage
collection.
Per cui la domanda che fai non la capisco molto. Nel caso di Python,
cambiando un filino il target, ha senso. La posso interpretare in due
modi:
1. perchè nell'implementazione C di Python il 'conto' dei riferimenti è
tenuto nell'oggetto e non nel 'puntatore'? 
La risposta è che C Python è implementato per l'appunto in C, ed è più
comodo usare puntatori 'standard' (invece che strutture-puntatore
'smart' che poi sarebbero da manipolare esplicitamente con funzioni
separate) e ficcare il campo riferimenti dentro l'oggetto stesso (oltre
che dopo tutto, se ci pensi, è anche il posto più appropriato dove deve
trovarsi, visto che appunto così tutti quelli che usano l'oggetto
trovano in un punto centralizzato  il numero di riferimenti).
2. perchè l'implementazione C di Python ha scelto reference counting
invece che mark & sweep, incremental, train whatever?
Ecchennesò è la mia risposta: probabilmente online trovi qualcosa,
oppure chiedi su iclp. Francamente è un dettaglio implementativo che
tendo a maledire quando devo scrivermi un modulo in C con le API C di
Python, per il resto lo ignoro [ da quel punto di vista, implementare un
modulo in Ruby è più comodo, fortuna che tanto in Python c'è ctypes...
].
Riguardo a Java puoi trovare chili e quintali di documentazione. IMHO
trovi una traccia della risposta nei post di Kiuhnm. I progettisti di
Java e della JVM hanno avuto la malsana idea di basare tutto
sull'accoppiata di concorrenza a thread e stato condiviso. In un mondo
del genere, dove il modo migliore per scrivere sw con performance atroci
è proprio incasinarsi con i thread e specialmente abusare dei
'synchronized'. Se per necessità 'di linguaggio' ci fossero lock su lock
ad ogni stronzatina sarebbe ancora peggio (e ribadisco Kiuhnm lo ha
spiegato molto più in dettaglio e meglio).
-- 
blog:  
http://www.akropolix.net/rik0/blogs
 | Uccidete i filosofi,
site:  
http://www.akropolix.net/rik0/
      | tenetevi riso e
forum: 
http://www.akropolix.net/forum/
     | bacchette per voi.

 Vedo che usi il termine GC e 'smart pointer' in modo un poco improprio.
In effetti io cerco di dedurre la teoria da quel poco di esperienza che 
ho fatto, quindi quello che dici mi ha chiarito un po' le idee. 
Soprattutto quando mi dici che il python in realta' usa i ref-count.
Pero' c'e' qualcosa che non mi torna. In effetti per me, che faccio da 
solo le mie strutture dati, il modo piu' naturale di utilizzare il 
ref-count è proprio quello di mettere il ref-count direttamente nella 
struttura dati, senza altri intermediari. Questo significa che i miei 
smart-pointers sono, di fatto, dei semplici puntatori con un overload 
dell'operazione di assegnazione.
Inoltre la tecnica del ref-count, mi pare, si applica solo alla memoria 
allocata dinamicamente. Dunque il ref-count, dal mio punto di vista, è 
sostanzialmente una tecnica di gestione della memoria. Mentre scrivo mi 
viene in mente che si potrebbe forse ridefinire l'operatore new in modo 
che allochi anche lo spazio per il ref-count in ogni operazione di 
allocazione. Questo permetterebbe di utilizzare gli smart pointers con 
ref-count anche con strutture dati che non lo prevedevano.
Purtroppo non ho mai fatto programmazione multithreading e quindi non 
riesco a seguire il vostro discorso sul problema dei lock. Magari 
chiedero maggiori spiegazioni nell'altra parte del thread.
E.

 maggiore è il numero di processori, più inefficiente è il locking).
Scusa l'ignoranza sul multi-threading, ma non capisco perchè bisogna 
lockare il bus. In realtà non so neanche di preciso cosa significhi... 
Hai voglia di spiegarlo?
E.

 chiedero maggiori spiegazioni nell'altra parte del thread.
Piu' multithreading di cosi' ;-)
Ciao,
Nicola

 chiedero maggiori spiegazioni nell'altra parte del thread.
I 'lock' entrano in gioco ogni volta che hai una risorsa (o un numero
finito di risorse) condivisa fra più agenti. Ci vogliono dei meccanismi
per impedire che i vari agenti non si incasinino fra di loro.
Il problema è creare azioni 'atomiche' che possano essere eseguite in
modo ordinato all'interno dello stesso thread, ma in qualunque ordine
globale: ovvero ciascuno dei thread può venire eseguito per un po', poi
cambiato, poi rieseguito e così via. Il thread attivo è sempre uno (non
andiamo a complicarci ora con il multicore).
Io posso fare esempi, scendere in dettaglio, dimmi tu. Non è una cosa
difficile, solo non è immediatamente ovvia (o meglio, programmare
sistemi complessi con i thread, senza determinati accorgimenti *è*
estremamente difficile, ma capire i problemi teorici etc, è abbastanza
standard).


 Piu' multithreading di cosi' ;-)
LOL :)


 andiamo a complicarci ora con il multicore).
So dell'esistenza di cose come semafori, per la programmazione 
multithread. Mi chiedo se esiste la possibilità di richiedere che una 
certa sequenza di operazioni venga eseguita senza essere interrotta. Per 
esempio nel codice seguente:
template<class T>
class SmartPtr {
   T * _ptr;
   void set(Object *p) {
     if ( --_ptr->refCount == 0)    // **
       delete _ptr;
     _ptr=p;
     if (_ptr)
	_ptr->refCount++;
   }
   public:
   // ...
}
sarebbe sufficiente avere garantito che il decremento e il controllo 
zero vengano eseguiti con una unica operazione atomica (o addirittura 
una unica istruzione per la CPU?). Non riesco a vedere altri problemi...
E.

...mi ero dimenticato di controllare che _ptr!=0:
template<class T>
class SmartPtr {
   T * _ptr;
   void set(Object *p) {
     if (_ptr) {
       if ( --_ptr->refCount == 0)    // **
         delete _ptr;
     }
     _ptr=p;
     if (_ptr)
     _ptr->refCount++;
   }
   public:
   // ...
}


 esempio nel codice seguente:
mi associo alla tua curiosità !
In effetti sembra una cosa fortemente dipendente dall'OS o 
dall'hardware (o persino a entrambi i livelli).
L'unica forma di cautela che conosco, molto più debole di quanto 
chiedi, in quanto evita solo inconsistenza tra memoria e 
registri (non so come sia gestita la cache), e si ottiene 
anteponendo "volatile" alla dichiarazione.
Non ho mai guardato se esistano API (parlo di Win) con cui 
"lockare" un certo segmento di memoria (ad es. in scrittura, o 
con lock totale), in modo che le variabili e strutture dati 
delicate vadano protette da modifiche ... probabilmente ci sono, 
ma non solverebbero il problema in modo atteso, in quanto il 
blocco proteggerebbe il segmento di memoria delicato, ma non 
eviterebbe che "terzi" potessero comunque tentare l'accesso, e a 
quel punto non so se verrebbero scatenati page fault o altre 
notifiche poco amichevoli alle funzioni ficcanaso, per cui 
bisognerebbe riscrivere parecchio codice che usa quelle 
strutture dati in maniera fault tolerant, pieni di try catch e 
simili (ammesso che l'errore reso dall'OS sia ragionevolmente 
gentile e non un'uccisione immediata).
Se la sicurezza del sincronismo ti interessa molto e la velocità 
delle operazioni per niente (cosa rara), in effetti potresti 
comunque ricorrere a semafori su files.
La protezione dell'accesso e le varie restrizioni lì sono 
implementati in modo facile, proprio per gestire concorrenza al 
file (Addirittura puoi lockare solo intervalli di dati e non 
l'intero file), e anche gli errori restituiti in caso di 
conflitto coi blocchi sono sempre trappabili dal codice utente, 
che non si vede ucciso ma solo reso un codice di errore.
Ovvio che è solo una patch grossolana, se avessi il problema 
cercherei nel manuale delle API.
Un modo possibile è mettere un indirezione ai puntatori, e 
restituire agli utenti solo HANDLE alla memoria, il cui accesso 
è filtrato, e centralizzato ... anche questo non è il massimo di 
efficienza, ma ad es. anche le antiche routine del memory 
manager di windows ragionavano a handle.
Fintanto che avevi un Handle valido a un blocco di memoria, il 
blocco era garantito esistere (e ne avevi privilegi), ma stava 
da qualche parte su disco (swap) o in ram. Se serviva usarlo in 
modo concreto, si chiedeva il Lock, si usava, e poi si 
sbloccava, per non intralciare il memory manager globale.
Non so come funga ora, su XP però.
boh, stiamo a vedere consigli migliori !
ciao
soviet_mario


 una unica istruzione per la CPU?). Non riesco a vedere altri problemi...
Tipicamente io la vedrei con due metodi nell'oggetto puntato retain e
release, i quali sono appunto atomici.
Come č possibile fare questo? Con un lock, a prescindere di come lo
stesso viene implementato. In molti casi questo puň accadere con
un'operazione della CPU. Ma non č necessario, ne garantito.
Il modo 'corretto' č un lock (dipendente comunque dalla piattaforma).
E se sai cosa č un semaforo, in pratica sai anche cosa sia un lock: un
semaforo binario č la forma piů semplice di lock.


 potutirestare ancora un po' nella cache.
Infatti ho fatto una distinzione tra multicore e multiprocessore.
E no, il problema non e' la scrittura su ram (quella dovrebbe avvenire
comunque prima o poi), ma l'invalidazione della cache (quale cache
viene invalidata sull'architettura multicore e quale su quella
multiprocessore?)Ma qui stiamo andando decisamente offtopic.
--*PaN!*
 
-- 
I'm using an evaluation license of nemo since 47 days.





Spiego rapidamente.
in un architettura multiprocessore (ma per certi versi anche in
unamonoprocessore con multithreading preemptive) alcune operazioni che
si suppone debbano essere eseguite in un colpo unico sono in realta'
spezzate.
x++ per esempio opera leggendo x dalla memoria, incrementandolo
eriscrivendolo in memoria. Se un altro processore decidesse di fare
la stessa cosa nel frattempo, potrebbe avvenire questa
divertentesequenza di eventi (ordinati temporalmente):
processore 1: legge x
processore 2: legge x (ha letto lo stesso valore del processore
1)processore 1: incrementa il valore letto
processore 2: incrementa il valore letto
processore 1: scrive il dato in memoria
processore 2: scrive il dato in memoria
ora, ho eseguito DUE istruzioni del tipo x++ (su due thread diversi),
ma x e' stato incrementato di 1 valore.
se le istruzioni venissero eseguite sequenzialmente il risultato
sarebbediverso:
processore 1: legge x
processore 1: incrementa il valore letto
processore 1: scrive il dato in memoria
processore 2: legge x (ha letto il valore gia' incrementato)
processore 2: incrementa il valore letto
processore 2: scrive il dato in memoria
per questo il primo processore che decide di incrementare x dovrebbe
farlo informando gli altri processori di non toccare x fino a nuovo
ordine, ovvero fino a quando non abbia finito di scrivere x
nuovamente. In questo modo le istruzioni vengono sempre eseguite in
sequenza, ed ilrisultato e' prevedibile.


 spezzate.
Ok, credo di aver capito... ci sono grossi problemi con le variabili 
condivise. Ma la cache della CPU come viene utilizzata? Diversi processi 
sulla stessa CPU condividono la cache, vero? Invece processi su CPU 
diverse avrebbero ognuno avere la sua cache. Addirittura se un processo 
passa da una CPU ad un'altra si ritrova con una cache diversa? Mi sembra 
un inferno...
E.

 ...mi ero dimenticato di controllare che _ptr!=0:
Infatti io per evitare queste cose encapsulerei refCount e ci accederei
solo con appositi metodi *non* virtuali.
Per cui dovrebbe essere piuttosto capace di inlineare e tutto, perdita
di efficienza poca e grosso guadagno di stabilità.
Non ho ben capito i tuoi puntatori. Li dichiari templatici, tuttavia fai
loro accettare un oggetto di tipo Object* e basta. Posso chiederti quale
è il senso?
Poi nel caso invece di if(_ptr) if...
if(ptr && --ptr->refCount == 0) ...
Che come dicevo io diventerebbe per lo meno:
if(ptr && ptr.release()) ...
con 
unsigned int Object::release();
Tipicamente l'inguacchio è che con refCount 'pubblico' se qualcuno
sminchia e te lo setta negativo, ti sei perso il tutto. Immagino che tu
lo abbia definito unsigned: nel caso sminchia comunque poichè te lo
mette 'grandissimo'.
L'altro vantaggio è che se trovi metodi efficienti per non modificare
tutte le volte il contatore (come alcuni accennati su wikipedia), in
questo modo il punto in cui fare il macello è unico.
Addirittura potresti anche considerare di applicare il NullObject
pattern (ma devi vedere come te la gestisci).
Ovvero crei un oggetto 'nullo' ma del tipo giusto che quando gli chiami
addosso le release noppa.
In questo caso ti risparmi ptr && ptr.release(). L'invariante della
classe è che _ptr sia un oggetto su cui puoi chiamare release, per cui
non fai il controllo.
Aggiungo infine che forse (ma dovrei spucliare lo standard) potresti
andare a ridefinire delete in modo che agisca *solo* se refCount è a 0.
O meglio: lo standard dice:
If the argument given to a deallocation function in the standard library
is a pointer that is not the null 
pointer value (4.10), the deallocation function shall deallocate the
storage referenced by the pointer, render- 
ing invalid all pointers referring to any part of thedeallocated
storage. 
Per cui quest'ultima mia idea sarebbe *undefined behaviour*. Ma sarebbe
'quasi' il top come comodità. Purtroppo non è accettabile.


 manager di windows ragionavano a handle.
Guarda che questo è normale in C++: tipicamente non si dovrebbe *mai*
maneggiare la memoria esplicitamente (se non quando è assolutamente
provato che è necessario) e gestirla centralizzata dentro una classe che
ne ha l'incarico. A questo punto si fa in modo che controlli anche gli
accessi concorrenti.
Ma poi voglio dire, chi offre thread offre anche svariate primitive di
sincronizzazione. Che so, guarda boost thread. Oltretutto, mi risulta
portabile assai.
Non fossimo su it.comp.lang.c++ suggerirei anche diversi funzioni posix
(che sono disponibili sui vari unices *e* su Windows), ma abbiamo boost
che fa il mestiere.
Confesso anche che *io* non ho mai usato boost (ho usato un pochetto
quelle di Qt, invece, ma non troppo, se non mi si dimostra che per un
dato compito i thread pongono un netto vantaggio, io uso processi o
altro).


 diversa? Mi sembra un inferno...
Tecnicamente al processo interessa poco, cosi' pure al thread.
E' la CPU che tiene sincronizzato il contenuto della cache, in modo
trasparente al processo e al thread. Tutta l'architettura deve
funzionare esattamente come se la cache non esistesse.
Il problema della cache e' indipendente dal locking: anche se i due
meccanismi interagiscono pesantemente, dall'alto tu vedi ben poco.
--
Marco
 
-- 
I'm using an evaluation license of nemo since 48 days.


 andare a ridefinire delete in modo che agisca *solo* se refCount è a 0.
Io penso che la cosa piu' chiara (e piu' sicura) da fare è rendere 
privati tutti i costruttori degli oggetti con refCount. La costruzione 
potra' avvenire solamente tramite funzioni statiche che ritornano un 
refPtr<T> invece che un T*. Infatti l'unico modo per evitare che 
l'utente possa fare danni e' non permettergli mai di accedere 
direttamente al puntatore.
E.

 funzionare esattamente come se la cache non esistesse.
Il sistema che utilizzo io ha due processori e i DMA. Uno dei due 
processori ha la cache, ma il DMA e l'altro processore no. Ne risulta 
che l'utilizzo della cache non è trasparente. Se i dati sono stati 
scritti dal processore secondario o dal DMA bisogna richiede 
esplicitamente un flush della cache.
E.

 portabile assai.
non ho mai fatto nessun'applicazione multithread, ne ho solo 
letto (ed è cosa alquanto diversa leggere e fare !)
ciao
Soviet
CUT

 Ma qui stiamo andando decisamente offtopic.
A volte è necessario.
Kiuhnm

 e la 
differenza si vede.
Kiuhnm

 letto (ed è cosa alquanto diversa leggere e fare !)
Certo: e posso dirti che fare (bene) è parecchio difficile. La prima
cosa a complicarsi è il debugging, la seconda è che le dimostrazioni
formali di correttezza (o anche semiformali) diventano un mezzo macello.
Per il resto se usi primitive di comunicazione 'furbe' fra thread te la
cavi anche bene. Se invece fai le cose in modo cinofallico è facile che
ti trovi in un pantano, con bachi difficili da tracciare e capire,
deadlock etc etc.
In generale la regola d'oro è: se sai quello che fai, sai anche quando
hai bisogno dei thread. Se non sai quello che fai, usando i thread ti
ficchi in pantano, per cui non farlo :)
Purtroppo ci sono legioni di programmatori cresciuti a pane e Java(tm)
che hanno idee bislacche sulla concorrenza, sui *vari* modelli di
concorrenza (ovvero non li conoscono) e pensano che la *via* siano i
thread. E invece sono uno strumento, neppure troppo comodo, anche se a
volte (ma solo a volte) insostituibile o quasi.


 formali di correttezza (o anche semiformali) diventano un mezzo macello.
Il programmatore medio è già fortunato a poter usare lock, semafori e 
simili, ma le cose si fanno *veramente* difficili quando si sviluppano 
sistemi lock-free e, peggio ancora, wait-free. Ancora ho gli incubi e 
spero di non dover più rimettere le mani nel codice già scritto.
Kiuhnm

 multicore.
mi sembra quindi di capire che l'atomicità è un concetto 
relativo, nel senso che ogni strato di software che non può 
interferire con alcuno di quelli sottostanti, considera atomiche 
chiamate che sembrano atomiche a lui (anche se magari non lo 
sono affatto per gli strati più profondi).
Se ho capito bene, in effetti mi sembra ragionevole.
Se non è ragionevole, non ho capito una fava :-)
ciao
Soviet
P.S. non sono un programmatore, per cui presumo che non avrò mai 
la necessità di imbarcarmi in applicazioni che richiedano queste 
garanzie di thread safeness

 deadlock etc etc.
Frasi come "se fai le cose bene, ti trovi bene e se fai le cose male
ti trovi male" da te non me le aspetto :-P

 garanzie di thread safeness
Non esiste la "safeness" in inglese, semmai esiste la "safety" :-)

 garanzie di thread safeness
In realtà *ogni volta* che usi i thread *devi* dare garanzie sulla
condivisione di risorse, altrimenti scrivi applicazioni che non
funzionano, sono scorrette e quant altro.
Il punto è che, banalmente, se non usi i thread puoi associare una
priorità di apprendimento piuttosto bassa. Se li usi, beh, allora direi
che la cosa diventa interessante.
Nota che a me i thread non piacciono, ma ci sono tanti motivi per cui
possono tornare comodi. Tipicamente nel calcolo 'scientifico'.


 ti trovi male" da te non me le aspetto :-P
Oh beh :) 
Giusto per non ripetere le solite cose. :)


 comunque stare in un registro.
Costa a livello di branch. I processori odierni (per i quali scriviamo)
si dedicano spesso a 'predire' quale codice  verrà eseguito e cominciano
a eseguirlo 'prima' di sapere se effettivamente un branch verrà o meno
preso.
Cercano di indovinare l'indirizzo della prossima istruzione da fetchare
(e poi decodificare, andare a prendere gli operandi, etc) prima che
quella corrente sia terminata. 
Per cui in generale i vari branch possono rallentare l'esecuzione di
molto, poichè svuotano la pipeline e fanno perdere performance. I
processori sono ottimizzati per stare con la pipeline piena. Per esempio
perchè se per ciascuno stadio del ciclo fetch-execute ci vanno x
nanosecondi e la pipeline ha y stadi, se processiamo un'istruzione per
volta, impieghiamo x*y nanosecondi, ovvero facciamo 1/(x*y) operazioni
al nanosecondo -> 10E3/(x*y) istruzioni al microsecondo.
Se invece la pipeline fosse idealmente *sempre* piena, trascorsa la fase
in cui riempiamo la pipeline, eseguiamo in effetti un operazione ogni x
nanosecondi (e se la pipeline è lunga, mi pare il P4 avesse 20 stadi,
vedi bene che la differenza non è assolutamente piccola). 
A questo punto viene il problema: quella di sopra è una misura ideale,
che si basa sul fatto che la pipeline non si svuoti *mai*. I valori
reali sono nell'intervallo del caso pessimo e del caso ottimo. Un buon
branch predictor ci fa avvicinare al caso ottimo: codice che lo aiuta
pure. In questo senso i salti condizionati sono molto più costosi di
quello che sembrano.
Per dire su asm PPC potevi anche usare dei flag per 'aiutare'
(conoscendo il codice) il processore con un hint su quale branch sarebbe
stato preso [ quando parliamo di branch parliamo di if, switch, while,
for, non ho presente l'asm generato dai gestori di eccezioni ].
Ecco, in questo senso dicevo che fare dei check sul not-null potrebbe
essere più costoso di quello che si pensa.
Non ho spiegato le cose benissimo e in effetti intervengono pure altri
fattori. Ma come cosa di massima direi che si potrebbe intuire il
problema. Poi si può approfondire da qualcuno che sia più vicino di me
all'hardware. 


 Costa a livello di branch. I processori odierni (per i quali scriviamo)
In effetti il processore su cui dovrà girare quel codice non ha i branch...
E.

 In effetti il processore su cui dovrà girare quel codice non ha i branch...
Cioè mi stai dicendo che è roba embedded. Perchè fin il Pentium MMX
aveva un (rudimentale) branch predictor. IMHO anche il primo Pentium.
Sul 486 non mi pronuncio.
Poi non è che dico che bisogna evitare if e while, eh. Dico solo che in
generale costano più di quello che pensa chi non pensa anche all'hw
sottostante. NB, spesso poi il bpredictor fa la cosa giusta e sei a
posto, eh. Dico solo, occhio. :)


 dice nulla sul come *č* implementata. Posso darti un'implementazione


 mia classe un puntatore per ogni classe parent con funzioni virtuali?
Se usi funzioni virtuali certo (o meglio aggiunge 'informazione').
Chiaramente questo accade anche se lo fai in presenza di ereditarietą
multipla. 


 multipla. 
Sì, ok, ma io dico che se eredito da N classi con funzioni virtuali lo 
spazio occupato dalla mia classe aumenta linearmente con N. Se è così 
questa è una cosa da sapere!
E.

 questa č una cosa da sapere!
Possibile. Ma direi che se stai ereditando da un numero N consistente di
classi hai qualche problema di design :)


 mia classe un puntatore per ogni classe parent con funzioni virtuali?
Mi hai incuriosito e mi sono documentato aggiuntivamente.
In pratica si, ogni classe base aggiunge un vptr (con alcune eccezioni,
alcuni possono essere condivisi. Per esempio Meyers fa come esempio
l'ereditarietà a diamante e mostra che un vptr può essere condiviso).
NB. Può.
Ad ogni modo il problema non è in se l'ereditarità multipla, ribadisco.
E' una features che a seconda di come la si usa può avere costo
rilevante o meno. Tipicamente nei casi in cui è comodo usarla (per
esempio per i Mixin) ce la si cava anche been. 
