Se dichiaro una variabile come Figlia f1, sizeof(f1) mi restituisce
12.
Se dichiaro una variabile come Madre m1, aggiungo dei dati in m1,
sizeof(m1) mi restituisce 12.
Mi aspettavo qualcosa di diverso, più pesante in termini di memoria
occupata.
Se devo passare un oggetto come parametro di una funzione, quanto
spreco c'è rispetto a passare un puntatore all'oggetto?
Girovagando un tantino su google ho letto che spesso i compilatori
trasformano sotto banco il passaggio di oggetti come riferimenti agli
oggetti.
Quindi oggetto vs puntatore dovrebbero essere equivalenti in termini
di velocità, stack usato, eccetera eccetera.
Quello che non mi torna sono i 12 bytes. Un normale puntatore ha
dimensione 4, la classe 12, praticamente il triplo.
Ora per fare una prova ho scrtitto questo:
in cui il puntatore ad intero occupa 4 bytes, quello all'oggetto 16.
Boh...
g++ 3.4.2 su Windows
su Mac non so esattamente, devo controllare.
Comunque in linea di massima sizeof(oggetto)>=somma(sizeof(membri))
poi bisogna vedere se ci sono di mezzo vtable o altri dettagli
implementativi...
e poi bisogna vedere come allinea il compilatore
insomma una regola generale non esiste
Nel tuo caso non hai funzioni virtuali e hai 2 membri.
Molto ragionevolmente l'int occupa 4 bytes e il vector ne occupa 12.
Sulla size del vector, poco da dire: anche questa � fortemente
dipendente dall'implementazione. Lo std non credo dica nulla a tal
proposito...
E quindi, tornando al discorso iniziale, passare un oggetto ad una
funzione è uguale a passarlo per riferimento? Fa tutto il compilatore?
Oppure conviene passare per riferimento?
In teoria per riferimento, cioè passo il puntatore, questo lo
immagino, ma non vorrei riscrivere tanto codice quando poi magari il
compilatore ottimizza per conto suo...
Corretto. Per esempio, se ben ricordo nell'ereditarietà multipla a
diamante con classi base virtuali, un puntatore extra per ogni classe
da cui si eredita viene salvato nell'oggetto.
> > e poi bisogna vedere come allinea il compilatore
> > insomma una regola generale non esiste
Corretto. Comunque la maggior parte dei compilatori su macchine a 32
bit allinea a 4 byte, a meno che non gli si dia un attributo di
"packing" per le strutture o le classi (cosa da NON fare se non si sa
ESATTAMENTE cosa implica; di solito è utile solo quando bisogna
leggere file binari copiando direttamente in memoria).
>
> > Nel tuo caso non hai funzioni virtuali e hai 2 membri.
> > Molto ragionevolmente l'int occupa 4 bytes e il vector ne occupa 12.
> > Sulla size del vector, poco da dire: anche questa è fortemente
> > dipendente dall'implementazione. Lo std non credo dica nulla a tal
> > proposito...
>
> E quindi, tornando al discorso iniziale, passare un oggetto ad una
> funzione è uguale a passarlo per riferimento? Fa tutto il compilatore?
Assolutamente no. E se il compilatore fa un cambiamento del genere,
sbaglia di grosso, a meno di messe inline di funzione laddove non ci
sono side-effects. Il passaggio per valore di un oggetto implica la
chiamata di costruttore di copia, e di distruttore alla fine della
funzione. Poiché tali operazioni possono portare a allocazione/
rilascio di risorse non determinabili a priori (apertura chiusura
socket, file, etc.), il compilatore *deve* eseguirli. Il compilatore
può eventualmente _elidere_ dei costruttori quando si ritornano degli
oggetti come valori, riservando spazio nello stack frame della
funzione chiamante e lavorando direttamente lì, tuttavia a) non
bisogna mai farci conto perché non è deterministico e b) ciò non ti
salva da un buon design. Prediligi sempre chiarezza di codifica a
performance sulla copia di un paio di byte per un paio di volte in un
programma da 20 mln. di righe.
Un oggetto passato per riferimento (meglio, se non si intendono fare
side effect, se costante) è sempre lo stesso, e non vengono effettuate
copie di nessun tipo.
> Oppure conviene passare per riferimento?
Conviene passare per riferimento costante sempre quando si può tranne
che per oggetti veramente banali; per riferimento non costante quando
il parametro è "inout" (cioè deve essere modificato), per valore
quando le modifiche devono rimanere locali al corpo della funzione.
Le variabili di tipi primitivi normalmente si passano per valore, a
meno di side-effect.
Il puntatore ovviamente ha una semantica diversa dai riferimenti
perché un riferimento è sempre costante, mentre un puntatore può
venire riassegnato.
> In teoria per riferimento, cioè passo il puntatore,
NO. Riferimento e puntatore sono DIVERSI. Riferimento -> r-valore,
puntatore -> l-valore.
Infatti:
int pippo, pluto;
&pippo = &pluto; // non funziona.
> questo lo immagino, ma non vorrei riscrivere tanto codice quando poi magari il
> compilatore ottimizza per conto suo...
Giusto, però temo che tu abbia ancora un po' di confusione sull'uso di
puntatori e riferimenti.
Ciao,
m.
> Mi aspettavo qualcosa di diverso, più pesante in termini di memoria
> occupata.
Stanley Lippman molti anni fa ha scritto un libro interessante su
questo argomento specifico.
E' vecchiotto (1995) ma te lo consiglio, se riesci ancora a trovarlo
(forse su Amazon):
"Inside the C++ Object Model"
Si in effetti credo di non aver capito la differenza tra riferimento e
puntatore o_O
Ero convinto che la differenza tra passaggio per valore e per
riferimento è che nel primo caso venga creata una copia dell'oggetto/
variabile, mentre nel secondo caso si usi un puntatore all'oggetto/
variabile...
Il succo è quello; solo nel secondo caso non si usa un *puntatore*,
bensì appunto un riferimento - che è l'indirizzo statico e costante al
quale si trova l'oggetto, ossia il suo l-valore. Questo è il motivo
per cui un riferimento (almeno, fino allo standard attuale del C++)
può essere solo un r-valore.
Sia f una funzione che assume un parametro formale, io lo posso
passare per:
* valore: con copia nel passaggio che viene distrutta alla fine dello
scope della funzione
* riferimento: passo un indirizzo di memoria al quale il compilatore
sa di trovare l'oggetto - solitamente soggetto ad ottimizzazioni come
quella che citavo nell'altro messaggio
* puntatore: passo una variabile con il valore che rappresenta
l'indirizzo di memoria al quale si dovrebbe (a meno di errori) trovare
l'oggetto
Il valore implica la copia, gli altri due metodi no.
Un riferimento è sempre costante, perché non è una variabile.
Un puntatore può essere riassegnato a piacere (a meno che non sia
dichiarato come costante ovviamente, per esempio: "int *const pippo").
Un puntatore può essere dereferenziato, e se ne può prendere lo l-
valore per inserirlo in un altro puntatore (per es. "int **pluto"). Un
riferimento no.
Ciao,
m.
> Comunque la maggior parte dei compilatori su macchine a 32
> bit allinea a 4 byte
Non credo. Io direi che "usa le stesse strategie che vengono usate per
le struct C". Indicativamente l'allineamento non e' a 4 byte: se hai due
char, per dire, il tutto IMHO non verra' posizionato dalle normali
implementazioni come
[x|-|-|-|x|-|-|-]
ma come
[x|x]
Idem con degli interi da 2 byte. Non ho tuttavia provato, sto andando a
memoria, quindi posso sbagliare.
--
-riko