Newsgroup: it.comp.lang.c++
Titolo: std::map

Perché non è possibile scrivere
std::map<std::string, std::string&>
E' una limitazione intrinseca dei template?
Ho dovuto ripiegare a
std::map<std::string, std::string*>
Kiuhnm


Non dei template ma dei container standard.
Il problema è che il tipo deve sottostare ad alcune limitazioni. Per 
esempio deve avere un costruttore di default (ovvero devi poter 
instanziare una variabile di quel tipo senza avere altre informazioni).
Cosa che una reference non fa.
Ma è parte del contratto dei container, non è una limitazione dei 
template. Si possono benissimo scrivere dei template che funzionano con 
parametri reference (anche se è molto molto raro che questo abbia un senso).
Tutto dipende da come è scritto il codice del template. Non dal 
meccanismo stesso dei template.

 instanziare una variabile di quel tipo senza avere altre informazioni).
Già che ci siamo, come posso inizializzare un reference?
Un puntatore lo inizializzo per es. a 0, ma un reference?
Il compilatore mi dice giustamente che non ho inizializzato un membro 
della classe, ma in quel momento non ha senso far puntare il reference a 
qualcosa. Ho ripiegato usando un semplice puntatore.
Kiuhnm

Kiuhnm ha scritto:

Non esiste un container che accetti chiave + elemento?
Della sola chiave non me ne faccio niente.
Kiuhnm

 Un puntatore lo inizializzo per es. a 0, ma un reference?
Il valore di una reference non può essere cambiato dopo l'inizializzazione.
Si riferirà sempre all'oggetto che denotava all'atto dell'inizializzazione.
Quindi dovresti inizializzarla già con l'oggetto che denoterà per tutta la
vita. La sintassi è quella standard
obj b;
obj &c = b;
-- 
USB Priests for only 10$


Il contenitore std::map<Key, Value> prende proprio chiave + elemento, 
Key e' il tipo della la chiave e Value il tipo dell'elemento.
Per quanto riguarda l'uso del tipo "std::string&", nessun contenitore 
della libreria standard e credo nessun contenitore in generale puo' 
contenere dei reference; solo valori o puntatori.
Siccome i reference devono necessariamente essere inizializzati, se si 
usa una variabile membro di un tipo reference, deve essere inizializzata 
nella lista di inizializzazione di tutti i costruttori.
In seguito fare' riferimento all'oggetto a cui e' inizializzato per 
tutta la vita dell'oggetto di cui e' membro.
Se nella lista di inizializzazione di un costruttore non si sa ancora a 
quale oggetto inizializzare la variabile, oppure se durante la vita 
dell'oggetto contenitore si vuole che la variabile si riferisca a un 
altro oggetto, allora non e' appropriato definire tale variabile di un 
tipo riferimento.
-- 
     Carlo Milanesi
 
http://digilander.libero.it/carlmila

 Della sola chiave non me ne faccio niente.
Ah, ho capito come fare, però è scomodo.
Devo creare una classe composta, definire l'operatore < e passarla come 
chiave.
Kiuhnm

 Della sola chiave non me ne faccio niente.
Ho sbagliato di nuovo: non avevo visto che si deve usare pair<>.
Kiuhnm

 Key e' il tipo della la chiave e Value il tipo dell'elemento.
[...]
Adesso sono stanco e sto fondendo.
Ho il seguente codice che non riesco a far funzionare.
std::map<std::string, std::auto_ptr<Expression>> references;
.
.
.
auto_ptr<Expression> ptr(new Expression());
references.insert(pair<string, auto_ptr<Expression>>(s.GetToken(), ptr));
Mi dice
error C2558: class 'std::auto_ptr<_Ty>' : no copy constructor available 
or copy constructor is declared 'explicit'
Kiuhnm

 or copy constructor is declared 'explicit'
Strano. AFAIK il copy constructor di std::auto_ptr non è explicit.
Che implementazione della STL stai usando?

 or copy constructor is declared 'explicit'
I conteniori della libreria standard non possono contenere oggetti di 
tipo auto_ptr<T>, qualunque sia T.
Il motivo e' spiegato dal messaggio d'errore, cioe' non sono oggetti 
inizializzabili con un altro oggetto dello stesso tipo.
Per rendere exception-safe una map, mi pare che le strategie possibili 
sono le seguenti:
* Inserire nella mappa gli oggetti invece che i puntatori agli oggetti.
* Incapsulare la map in un apposito oggetto che si occupa di deallocare 
gli oggetti posseduti dalla map.
* Dichiarare la map come membro di un oggetto (che contiene anche altri 
membri), e deallocare gli oggetti posseduti dalla map nel distruttore 
dell'oggetto.
* Inserire nella mappa i puntatori a oggetti allocati da un gestore di 
memoria dotato di recupero automatico della memoria, o tramite 
garbage-collection o tramite reference-count.
* Inserire nella mappa degli smart-pointer diversi da auto_ptr, come 
boost::shared_ptr. Quest'ultimo pero', mi pare che abbia prestazioni 
scadenti in applicazioni multi-threading.


 Che implementazione della STL stai usando?
Quelle del VC8, suppongo.
Kiuhnm

 Conosci il motivo di questa ulteriore complicazione?
perchè std::auto_ptr non ha la semantica adatta per essere utilizzato 
all'interno di un contenitore.
-- 
"Quando Chuck Norris fa le flessioni, non alza sè stesso, abbassa la Terra."
"buuuhhhhhhhhhhrrrrrrr"


std::map ti permette anche di scrivere
mappa[chiave] = elemento;
Che pero' e' meno efficiente di scrivere
mappa.insert(tipo_mappa::value_type(chiave, elemento));
Ciao,
Nicola

 sono le seguenti:
[...]
Per scrivere codice veramente sicuro bisognerebbe praticamente usare rc 
ovunque. Ci credo che la gente ricorre al GC.
Dovrò trovare un metodo più sistematico e trasparente per gestire la cosa.
Kiuhnm

 ovunque. Ci credo che la gente ricorre al GC.
Nella mia esperienza, in C++ sono poco usati sia il reference count che 
la garbage-collection.
Le tecniche piu' comuni sono le seguenti:
* Inserire nella mappa gli oggetti invece che i puntatori agli oggetti.
* Dichiarare la map come membro di un oggetto (che contiene anche altri 
membri), e deallocare gli oggetti posseduti dalla map nel distruttore 
dell'oggetto.
* Non assegnare alla map la ownership degli oggetti puntati.
Le prime due tecniche erano gia' state citate in un messaggio 
precedente, nel quale mi ero dimenticato dell'ultima tecnica.


 * Non assegnare alla map la ownership degli oggetti puntati.
Propendo per la seconda.
La prima è semplice, ma inefficiente.
La terza sposta semplicemente il problema.
Kiuhnm

 all'interno di un contenitore.
Credo che lui parlasse della necessità dello spazio.
E comunque quello che dici tu è vero (in parte) ma non ha nulla a che 
vedere con la compilabilità del codice.

 Quelle del VC8, suppongo.
Nota che questo errore non ha nulla a che vedere con il discorso del >> 
invece che > >.
Non ho VC8 (e neppure windows, se per questo).
Una volta MS usava le dinkumware. Sul sito della dinkumware c'è la 
documkentazione completa della STL. Presumibilmente della loro 
implementazione della STL. Ma (ho verificato) il copy constructor non è 
specificato come explicit.
Può essere che abbia letto male qualche cosa, ma credo che faccia prima 
a verificarlo tu: verifica negli header di sistema quale è la 
dichiarazione della classe auto_ptr, e specificatamente del copy 
constructor.

 scadenti in applicazioni multi-threading.
In generale direi che quello che dici è corretto. Ma esistono anche 
altre possibilità. Tutto dipende dal tuo design.
Se per esempio è possibile GARANTIRE per DESIGN che la lifetime degli 
oggetti è gestita altrove ed è superiore a quella della loro presenza 
nella map, puoi benissimo inserire nella map i puntatori e fregartene.

 precedente, nel quale mi ero dimenticato dell'ultima tecnica.
E io ti ho risposto a quel messaggio indicando proprio questa 
alternativa ... prima di leggere questo :-(
Ma ne approfitto per una nota all'alternativa dell'rc che in quel 
messaggio tu indichi come scarsa in caso di multithreading (almeno con 
le boost).
Non conosco le boost. Ma in generale il rc č una cattiva idea in se e 
per se nei sistemi multithreaded. A meno che il programmatore non faccia 
una cosa simile al discorso del non-assegnamento della ownership. Ovvero 
utilizzi il rc implementato in single thread mode nonostante il 
programma sia multithreaded e garantisca by design che gli oggetti in 
questione non siano usati da piů thread.


Non sono del tutto d'accordo.
Non mi pare che la prima sia inefficiente. Tranne nei casi in cui la 
maggior parte dei puntatori sarebbero NULL. Ma in questi casi ... perché 
non limitarsi a non avere proprio il nodo nella map (cosa che rende la 
map più efficiante, tra l'altro)?
IMO il problema della prima soluzione è che non puoi utilizzarla quando 
gli oggetti fanno parte di più container contemporaneamente.
Ma in questo caso è molto probabile che ci sia (o che possa essere 
introdotto appositamente) un container in cui gli oggetti hanno 
'presenza' che è garantita essere superiore alla presenza negli altri 
container. E a questo punto li si inserisce come oggetti in questo 
(prima soluzione) e come puntatori negli altri (terza soluzione).
La seconda, tra l'altro, è una soluzione parziale. Se la map ha la 
ownership degli oggetti, allora questo dovrebbe essere "riflesso" 
nel'interfaccia della map, il che non è.
I casi sono due: se non è garantita la ownership esclusiva per tutti gli 
oggetti, sei obbligato ad usare il rc. Se è garantita ... puoi ancora 
usare il rc, con l'accortezza di usare una implementazione 
single-threaded anche se il programma è multi threaded.

 constructor.
L'errore lo segnala qui:
		// TEMPLATE STRUCT pair
template<class _Ty1,
	class _Ty2> struct pair
	{	// store a pair of values
	typedef pair<_Ty1, _Ty2> _Myt;
	typedef _Ty1 first_type;
	typedef _Ty2 second_type;
	pair()
		: first(_Ty1()), second(_Ty2())
----------->	{	// construct from defaults
		}
	pair(const _Ty1& _Val1, const _Ty2& _Val2)
		: first(_Val1), second(_Val2)
		{	// construct from specified values
		}
	template<class _Other1,
		class _Other2>
		pair(const pair<_Other1, _Other2>& _Right)
		: first(_Right.first), second(_Right.second)
		{	// construct from compatible pair
		}
	void swap(_Myt& _Right)
		{	// exchange contents with _Right
		std::swap(first, _Right.first);
		std::swap(second, _Right.second);
		}
	_Ty1 first;	// the first stored value
	_Ty2 second;	// the second stored value
	};
Eccoti l'inizio della classe auto_ptr:
template<class _Ty>
	class auto_ptr
		{	// wrap an object pointer to ensure destruction
public:
	typedef _Ty element_type;
	explicit auto_ptr(_Ty *_Ptr = 0) _THROW0()
		: _Myptr(_Ptr)
		{	// construct from object pointer
		}
	auto_ptr(auto_ptr<_Ty>& _Right) _THROW0()
		: _Myptr(_Right.release())
		{	// construct by assuming pointer from _Right auto_ptr
		}
Kiuhnm

 nel'interfaccia della map, il che non è.
Ma infatti la map non ha l'ownership, nella seconda soluzione.
La seconda soluzione è in realtà è analoga alla terza, con la differenza 
che viene specificato chi deve avere l'ownership.
Kiuhnm

 questione non siano usati da più thread.
Non vedo il problema. Il rc, se applicato ad oggetti sufficientemente 
grandi, è molto vantaggioso e le implementazioni lock-free o wait-free 
non sono difficili da realizzare. Le CAS sono abbastanza efficienti.
Il mio micro-OS, per es., è totalmente wait-free ed è molto più 
efficiente di tutti gli OS che usano ancora le sezioni critiche o i 
mutex (quasi tutti, praticamente).
Kiuhnm

         }
Abbiamo un costruttore di copia che non è explicit (come lamenta il 
messaggio di errore) e un costruttore di default (ottenuto tramite il 
default di un parametro, ma per il linguaggio se tutti i parametri hanno 
un default allora quello è anche un costruttore di default) che è 
dichiarato explicit. Ed è proprio su quello che il compilatore inciampa.
La conclusione, come ti è già stato detto, è che auto_ptr non va usato 
nei cointainer (e il motivo non è solo che non compila, ma che, se anche 
compilasse, probabilmente non otterresti quello che ti aspetti perchè 
auto_ptr non ha la semantica di un valore).
Peraltro il compilatore se ne esce con un messaggio di errore 
completamente assurdo.
Avrebbe dovuto darti un errore ... diverso da quello che ti ha dato.

 che viene specificato chi deve avere l'ownership.
No. La seconda soluzione presume che la map abbia l'ownership degli 
oggetti, la terza presume che non lo abbia. La seconda richede codice 
(probabilmente molto, se non vuoi rischiare di trovarti nei casini). La 
terza non ha codice (e quindi neppure bachi) ma solo un contratto di 
specifica.

 grandi, è molto vantaggioso 
Vero, in parte. Ma il tuo ragionamento ha ingannato molti programmatori. 
MOLTI.
Domanda: stai usando il rc per gestire la lifetime di un oggetto 
condiviso o stai usandolo per "risparmiare" la copia di oggetti che non 
vanno condivisi? (caso classico: le stringhe). Il fatto che tu parli di 
oggetti grossi fa sospettare fortemente questo secondo caso.
E nel secondo caso l'uso del rc è quasi sempre un errore in ambiente 
multithreaded.
Nell'altro caso il rc è efficiente se usato in maniera accorta. Ovvero 
se accompagnato da una buona gestione della ownership ogni volta che 
questa è fattibile, in modo che il rc avvenga solo in rari casi.
Se hai codice in cui avvengono migliaia di copie o di 
costruzioni/distruzioni di puntatori all'oggetto (per esempio in un 
ciclo in cui un puntatore all'oggetto viene passato come argomento a 
delle funzioni di calcolo) e in tutti i casi scatta il rc, allora 
diventa inefficiente, indipendentemente dalla dimensione dell'oggetto.
Ricorda che anche implementazioni lock-free, in ambiente multithreaded 
non possono esimersi dalle memory boundaries. E questo significa un 
sacco di cicli macchina.

 Avrebbe dovuto darti un errore ... diverso da quello che ti ha dato.
Dalla M$ non mi aspetto niente di più e niente di meno.
Grazie per la pazienza.
Kiuhnm

 oggetti nel distruttore!
Infatti non lo fa: è il distruttore dell'oggetto che ingloba la map a farlo.
 >>>
* Dichiarare la map come membro di un oggetto (che contiene anche altri 
membri), e deallocare gli oggetti posseduti dalla map nel distruttore 
dell'oggetto.
<<<
Kiuhnm

 oggetti grossi fa sospettare fortemente questo secondo caso.
Non sto usando l'rc; il mio è un discorso generale.
L'ho visto usare solo come (ormai superata) alternativa alle sezioni 
critiche e lì vale chiaramente la pena.
Kiuhnm

 sacco di cicli macchina.
Che cosa sono le "memory boundaries", e in che modo rallentano il RC?


 token, ma a fare l'"analisi logica" di quello che esamina.
Normalmente non è il parser a dividere il sorgente in token, ma il 
cosiddetto lexer (lexical analyzer), che riceve in input una sequenza di 
caratteri ed emette un sequenza di token,
Il parser riceve la sequenza di toker.
Nel caso della sequenza di caratteri ">>", il lexer del C++ genera il 
token "scorrimento-verso-destra", dopo di che il parser non può farci 
niente.


 Grazie per la pazienza.
Figurati. In realtà anche io, come qualcun altro, avevo cominciato a 
risponderti che auto_ptr non dovevi usarlo in un container.
Ma poi ho letto meglio il testo dell'errore e mi sono incuriosito: non 
aveva senso.
E come sai, quando qualche cosa sembra non avere senso, l'interesse 
cresce :-)
Nella mia prima risposta non specificavo che auto_ptr non ha la 
semantica corretta, perchè presumevo che la questione sarebbe comunque 
ritornata in un successivo momento durante l'analisi del problema.
Ho immaginato subito un "errore nel testo dell'errore" (annche perchè 
avevo riconosciuto il formato del messaggio che inizia con C1234 come 
microsoftiano) ma all'inizio pensavo ad una cosa diversa: il copy 
constructor di auto_ptr esiste e non è explicit: ma non accetta un 
inizializzatore costante.
Invece ... l'errore non ha nulla a che fare con un copy constructor. 
Misteri di VC.


Per come la interpreto io "posseduti dalla map" significa proprio che la 
map ne ha l'ownership. E dato che il post che citi (di Carlo Milanesi) 
parlava di alternative al gc e al rc, direi che lui intendeva dire che 
c'è un wrapper intorno alla std::map. Ovvero che hai costruito una map 
speciale riutilizzando l'implementazione std::map. Non che hai un 
generico oggetto esterno che gestisce varie cose e che internamente usa 
una map per una parte del suo funzionamento. Ma che il wrapper è in se e 
per se una map  specializzata (ovvero che la sua funzionalità esterna 
appare abbastanza simile a quella di un container dizionario, come 
appunto le map).
Se l'oggetto esterno ha una funzionalità più ampia (caso estremo 
l'oggetto "application") allora non siamo più nel caso 2 ma nel caso 3 
(ovvero l'ownership è esterna al più esterno oggetto che funziona come 
un contenitore dizionario).

 Che cosa sono le "memory boundaries", e in che modo rallentano il RC?
Chiedo scusa per l'utilizzo di un termine incorretto: memory barriers, 
non boundaries.
Call for action: se qualcuno (Nicola?) ha un link decente (magari in 
italiano) in alternativa a quello che posto in coda a questo post, si 
faccia avanti.
Comunque provo a dare un primo "accenno".
Forse non è necessario, ma faccio una premessa su cosa è l'rc.
Significa reference counting (o semplicemente count) ed è uno dei 
meccanismi utilizzati per gestire la lifetime degli oggetti.
Nella sua implementazione più semplice è "invasiva" (ovvero per 
implementare la rc quando non c'è bisogna cambiare l'oggetto). In poche 
parole si tratta di inserire fra i dati dell'oggetto stesso un contatore 
dei riferimenti, assicurarsi che l'oggetto sia sempre e comunque 
dinamico (allocato nell'heap) e assicurarsi che ogni nuovo riferimento 
creato alla stessa istanza incrementi il contatore e ogni riferimento 
che cessa di puntare all'istanza lo decrementi e, se zero, disallochi 
l'oggetto.
In queswto modo non è più necessario fare dei delete espliciti. Il 
meccanismo puù essere automatizzato usando degli smart pointer invece 
che dei puntatori.
In ambiente monothread la cosa è abbastanza efficiente perchè non c'è 
necessità di fare lock (ovvio). In ambienti multithreaded ogni nascita 
di un nuovo riferimento all'oggetto o cessazione di un riferimento 
significa modificare l'oggetto, e quindi implica un lock.
Un lock è estremamente costoso. Pertanto una ottimizzazione è quella di 
sfruttare il fatto che i dati da "lockare" sono semplicemente un 
contatore. Quindi invece che acquisire un lock (mutex, semaforo o che 
altro) è sufficiente un'operazione atomica. Ovvero una di quelle 
istruzioni assembler che leggono una locazione di memoria, incrementano 
(o decrementano) il valore contenuto, salvano il risultato nella 
locazione stessa e infine restituiscono il valore (iniziale, o dopo la 
modifica a seconda del caso) tutto in un'unico passo non interrompibile. 
Questo significa non solo che non può avvenire un interrupt a metà del 
processo, ma anche che due processori distinti non possono fare la 
stessa operazione contemporaneamente.
Questo evita la necessità di acquisire un mutex, o un altro tipo di lock 
a livello di sistema operativo.
Ma è comunque una operazione estremamente dispendiosa. Non basta che il 
procressore esegua l'operazione in un blocco unico: deve anche 
sincronizzarsi con gli altri processori, ovvero deve assicurarsi che 
tutte le operazioni precedenti (eseguite su qualunque processore) siano 
visibili in memoria e che l'operazione divenga immediatamente visibile 
in memoria da parte degli altri processori. E questo rende le cose molto 
complesse e dispendiose a causa dello scarico/carico delle cache che 
richiede molti cicli macchina.
Mi rendo conto che la spiegazione è abbastanza "superficiale". Ma il 
discorso sarebbe piuttosto lungo. Il problema del locking e del 
multithreading è MOLTO complesso. Un punto di partenza (e per molti di 
arrivo, vista la complessità del discorso) è la "guida non affidabile al 
locking" che trovi (in inglese) a questo indirizzo:
http://kernelbook.sourceforge.net/kernel-locking.pdf

 Che cosa sono le "memory boundaries", e in che modo rallentano il RC?
Per questioni di efficienza, non è possibile prevedere l'ordine 
d'esecuzione delle istruzioni sui vari processori e inoltre la memoria è 
garantita essere consistente soltanto riguardo il singolo processore.
Gli algoritmi lock-free/wait-free sono diversi da quelli classici perché 
non serializzano l'accesso ai dati. Solitamente i programmatori usano le 
sezioni critiche per serializzare l'accesso, mentre i metodi LF e WF 
permettono l'accesso simultaneo sempre e comunque. Il problema è che 
bisogna prevedere tutti i possibili casi. Se A inizia a fare 
un'operazione e viene interrotto, B deve poter eseguire la sua 
operazione tranquillamente. Questo significa che A deve lasciare la 
struttura dati in uno stato consistente. Al limite B potrebbe annullare 
i cambiamenti eseguiti da A, ma A potrebbe riiniziare l'esecuzione 
proprio in quel momento. A e B potrebbero anche essere eseguiti 
contemporaneamente e così via.
Tanto per darti un'idea, ho implementato varie versioni di alberi 
ribilancianti e tutte di almeno 4000 righe di codice C, senza contare le 
primitive di sincronizzazione. Le versioni normali (con le sezioni 
critiche) sono di circa 200 righe.
I metodi LF/WF assumono che la memoria sia coerente a livello di sistema 
e non solo di processore.
Assumi che A sia un thread eseguito sulla cpu 1, mentre B sulla 2. Se A 
scrive il valore 1 in var1 e poi il valore 2 in var2, può succedere che 
B veda var = 2, ma var1 != 1. I metodi LF/WF pretendono che le modifiche 
eseguite dalle "mov" siano immediatamente visibili a livello globale 
(fortunatamente basta garantire ciò in pochi punti strategici).
Per avere una descrizione esauriente di come i processori gestiscono la 
cosa, leggiti per es. il capitolo 7 del manuale Intel "Software 
Developer's Manual, volume 3: System Programming Guide". Vedrai che ci 
sono vari modi per rafforzare la consistenza della memoria (istruzioni 
serializzanti, memory type range registers (MTRR), ecc...).
Kiuhnm

 map ne ha l'ownership. E dato che il post che citi (di Carlo Milanesi) 
Se ne avesse l'ownership verrebbero distrutti nel distruttore della map, 
non in quello dell'oggetto che contiene la map. Inoltre c'è appunto 
scritto "la map come MEMBRO di un oggetto", non come classe base. Non mi 
pare ci sia molta libertà d'interpretazione.
Comunque i metodi sono piuttosto scontati, direi. Quello che 
m'interessava sapere era più che altro perché il compilatore non mi 
accettava il codice. E' chiaro che c'è quasi sempre il modo di ovviare 
all'inconveniente cambiando approccio o ripiegando in qualche modo.
Kiuhnm

 Misteri di VC.
Amen.
Kiuhnm


Non ho capito a cosa ti riferisci.
Un compilatore C++ (conforme) riconosce >> come un unico token. Sempre. 
Se vuoi due token sevi metterci lo spazio.
Se ti riferisci al discorso delle macro ... si comporta esattamente come 
ho descritto.

 un contenitore dizionario).
Avevo scritto che sono usate varie tecniche per gestire la lifetime 
degli oggetti memorizzati in un contenitore (nella fattispecie una map), 
tra cui le seguenti due:
* Incapsulare la map in un apposito oggetto che si occupa di deallocare 
gli oggetti posseduti dalla map.
* Dichiarare la map come membro di un oggetto (che contiene anche altri 
membri), e deallocare gli oggetti posseduti dalla map nel distruttore 
dell'oggetto.
In un messaggio successivo aggiungevo che solo la seconda delle due e' 
di uso comune, nel senso che la prima, a quanto mi risulta, e' ben poco 
usata.
Un esempio di questa seconda tecnica e' il seguente.
In un programma di videoscrittura ho una classe Documento, che contiene 
come variabile membro un puntatore a un array di caratteri, che contiene 
il testo del documento.
Supponiamo di non usare ne' auto_ptr, ne' string, ne' altri contenitori.
Questa variabile membro e' dichiarata come segue:
   char *t;
Per evitare memory leak, nel distruttore di Documento deve essere chiamata:
   delete [] t;
Supponiamo adesso che la classe Documento abbia un'altra variabile membro:
   char *t2;
Questa variabile "t2" punta all'interno dello stesso array puntato da 
"t", oppure punta all'array puntato dalla variabile "t" di un'altro 
oggetto Documento.
In entrambi i casi, sarebbe un errore mettere nel distruttore di 
Documento la seguente istruzione:
   delete [] t2;
Io esprimo questo fatto dicendo che "t" ha la ownership del buffer, 
mentre "t2" non ce l'ha.
Dall'esterno della classe Documento, il proprietario dell'array e' 
l'oggetto Documento, ma all'interno di una funzione membro di Documento, 
e in particolare all'interno del distruttore, il programmatore deve 
sapere che "t" e' proprietario di un buffer e "t2" non e' proprietario 
di niente.


 Non ho capito a cosa ti riferisci.
Ti sto chiedendo come si comporta nell'esempio da te proposto.
Ho sempre saputo che le macro vanno applicate PRIMA che il compilatore 
inizi ad esaminare il codice.
Che senso ha che il compilatore riconosca ">>" se in seguito tale token 
viene spazzato via dalla macro?
Kiuhnm
P.S. Nel mio parser posso benissimo dare la precedenza a >> rispetto a 
 >, a &= rispetto a &, ecc...
Però in caso di fallimento, prova anche con gli altri.

 >         second(_Ty2())
*e'* esplicita.
 
Ciao,
Nicola

 viene spazzato via dalla macro?
Infatti non  succede. Il preprocessore opera a livello di token e ">>"
non combacia con 
"> >".
Ciao,
Nicola

 viene spazzato via dalla macro?
Infatti non  succede. Il preprocessore opera a livello di token e ">>"


 std::auto_ptr richiede un typename e non una costante numerica).
E' sempre un errore di sintassi. Al compilatore basterà esaminare la 
definizione di auto_ptr<> per notare l'errore.
Kiuhnm

 "> >".
Se scrivo
#define references 1 > > pippo
voglio evidentemente che il preprocessore sostistuisca tutte le 
occorrenze di "references" con "1 > > pippo".
e che esamini l'espressione risultante come se l'avessi scritta io 
direttamente.
Il fatto che tutti (o quasi) la prima volta scrivano ">>" fa capire 
quanto questa sia una limitazione del linguaggio, piuttosto che una 
caratteristica.
Se per es. venisse introdotto un operatore "))" non potremmo neanche più 
usare le tonde liberamente. E' assurdo.
Diamo pure la precedenza a ">>", ma quando vediamo che non va bene, 
consideriamo anche l'altra alternativa. Non vedo come questo dovrebbe 
complicare le cose. C'è un lieve aumento dei tempi di parsing, ma non un 
aumento di complessità.
L'esempio di AnalogFile non dimostra niente; si limita a documentare il 
comportamento del compilatore.
La tokenizzazione iniziale è, secondo me, un modo molto primitivo di 
gestire il codice perché la suddivisione dovrebbe dipendere dal 
"contesto sintattico".
Kiuhnm

 caratteristica.
Il problema e' noto e qualcuno si sta dando da fare per metterlo a
posto:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1757.html
Ciao,
Nicola


E' un errore semantico, non di sintassi.
E se invece che auto_ptr ci fosse un template che accetta un intero non 
sarebbe comunque un errore.

 valore temporaneo.
Vero. Mi sbagliavo.
Ma l'errore dato dal compilatore è comunque fuorviante.
Dice che il costruttore per copia non esiste o è explicit.
In realtà esiste e non è explicit.
Quindi, come sospettavo fin dall'inizio (l'ho anche scritto in un altro 
messaggio, chissà perché poi mi sono perso via) il probelma è un 
costruttore di copia che non accetta inizializzatori costanti.

 sarebbe comunque un errore.
Ma infatti l'errore di sintassi non avviene in quella riga, ma 
nell'instanziazione di auto_ptr<>. E' sempre sintattico perché un nome 
di un tipo non può iniziare con un valore numerico.
Kiuhnm

 Dice che il costruttore per copia non esiste o è explicit.
Beh, in realtà dice che non c'è un costruttore copia disponibile. Quello che
c'è, infatti, non è disponibile.
Max


Un errore è sintattico se ha a che fare con la sintassi del linguaggio, 
ed è semantico se è per rilevarlo bisogna dare un significato ai 
simboli. Un errore sintattico è tale indipendentemente da cosa significa 
auto_ptr.
Riguardo a "un nome di un tipo non può iniziare con un valore numerico" 
sono d'accordo. Ma non c'entra. Sintatticamente fra le < > di un 
template ci può stare il nome di un tipo come ci può stare un valore 
numerico o anche una espressione.
template <unsigned I>
class MyClass {
};
MyClass<3> a;
MyClass< 7>>1 > b;
MyClass< 18-3*2<<1 > c;
In questo codice 'a' e 'b' sono istanze della stessa classe, 'c' è 
un'istanza di una classe completamente diversa.

 };
Come sopra, dipende dall'implementazione, però è probabile che sia come 
dici perché si fa prima a usare l'analisi semantica piuttosto che ad 
applicare il template come una macro e poi a rieseguire il parsing sul 
risultato.
Kiuhnm

 mappa.insert(tipo_mappa::value_type(chiave, elemento));
Suppongo ci sia differenza tra quello quotato e questo vero:
mappa.insert(std::make_pair(chiave, elemento));
... o i compilatori sono tanto furbi da collassarlo al primo?
Ciao,
  Gabry

 mappa.insert(std::make_pair(chiave, elemento));
Teoricamente questa invoca una conversione in piu' se gli argomenti non
sono esattamente del tipo di chiave ed elemento. Per il resto non
dovrebbero esserci differenze.
Ciao,
Nicola