Newsgroup: it.comp.lang.c++
Titolo: Ritorno da metodi get. Megio puntatori o ref?

Salve a tutti.
Quando scrivo un metodo "accessor" get, per ritornare un tipo complesso, 
sostanzialmente ho 2 alternative:
struct mytype
{
int b;
double c;
char d[200];
};
class test
{
public:
const member* GetMember_ptr() const {return &member;}
const member& GetMember_ref() const {return member;}
private:
mytype member;
};
//esempio di utilizzo
int main()
{
int i;
test t;
const member& m = t.GetMember_ref(); //stessa riga
i = m.b;
const memper* pm;
//....
pm = t.GetMember_ptr();
i = pm->b;
}
Sostanzialmente credo che il compilatore produca lo stesso codice per le 
due versioni. Siete d'accordo?
Entrambe le versioni dovrebbero essere le più efficienti possibili: non 
richiedono copie inutili.
Dal punto di vista del chiamante, preferisco la versione col puntatore. 
Mi lascia libero di dichiarare la variabile che ospita il ritorno e di 
assegnarla su righe diverse. In certi casi credo che questa flessibilità 
in più possa fare comodo.
Tuttavia la versione col riferimento mi sembra più pulita, proprio 
perchè obbliga il chiamante a inizializzare il riferimento. Anzi direi 
che è più "moderna", o java style...
Tracciando un bilancio preferisco la flessibilità del puntatore alla 
pulizia del riferimento (in fin dei conti il c++ non è per sprovveduti)
Cosa ne pensate?
Quale versione usate?
Oppure ci sono alternative?
Grazie
eg

 pulizia del riferimento (in fin dei conti il c++ non è per sprovveduti)
Vero, il C++ non e' da sprovveduti, pero' e' da sprovveduti lasciare in
giro riferimenti (in senso lato) a elementi interni di un oggetto del
quale non si sa fino a quando rimarra' in vita. E' meglio scrivere
codice che rende esplicito il possesso del membro dato 'member' ed il
modo per fare cio' e' usare uno smart pointer.
Se l'accesso a 'member' deve effettivamente essere condiviso tra piu'
oggetti userai uno smart pointer con reference count; puoi scaricarti
le librerie Boost da 
www.boost.org
 e studiarti boost::shared_ptr . Se
invece la tua classe 'test' fa solo da "fabbrica" di oggetti 'mytype' e
non ha piu' bisogno di questi una volta passati ad un oggetto cliente
puoi usare un puntatore a possesso esclusivo e qui la scelta piu' ovvia
e' std::auto_ptr dalla libreria standard.
Il mio consiglio e' comunque di scaricare Boost e usare
boost::shared_ptr in ogni caso.
Ciao,
Nicola

Ermes Gaiazzuppi wrote:
oltre alle risposte assai più puntuali ed approfondite della mia 
che hai già avuto, personalmente ti dico una mia preferenza :
quando la funzione NON può teoricamente incontrare errori (e 
quando ad es. deve essere usato come LValue), preferisco le 
REFERENCE per la sintassi più immediata nelle espressioni 
(dereference col punto, assegnamenti diretti senza 
dereferenziare e così via).
Quando però, maggior parte dei casi, sono possibili errori 
imprevredibili, preferisco passare il puntatore, dato che in tal 
caso si possono riservare valori illegali (ZERO nella 
fattispecie, o NULL) discriminabili da un qualsiasi altro 
indirizzo valido, in modo che la funzione possa segnalare al 
chiamante che non ha potuto fornire un address valido.
Nulla di più critico che passare una reference invalida a un 
chiamante che non può testarne la validità per confronto con null.
Poi, ripeto, di certo per esigenze più generali e per scrivere 
codice più elegante e sicuro, meglio seguire i consigli che hai 
già avuto
ciao
Soviet-Mario


 boost::shared_ptr in ogni caso.
E' anche da sprovveduti non tener conto delle prestazioni quando si 
programma in C++. Chi sceglie oggi questo linguaggio lo fa soprattutto 
perche' consente ottimizzazioni impossibili con altri linguaggi.
Se si fa un loop banale (ma cosi' banale che venga eliminato 
dall'ottimizzatore) e invece del seguente metodo:
   const mytype& GetMember_ptr() const {
     return &member;
   }
si chiama il seguente:
   shared_ptr<mytype> GetMember_shared() {
     return shared_ptr<mytype>(&member);
   }
Si ha un degrado di prestazioni di un fattore superiore a 300 (cioe' 
30000%).
Ma questo in realta' non ha senso in quanto per gestire un oggetto 
tramite smart pointer deve essere uno smart pointer fin dall'inizio.
Quindi si e' costretti a modificare anche la classe "test" sostituendo 
il seguente membro:
   mytype member;
con il seguente membro:
   shared_ptr<mytype> member;
e di conseguenza tutti gli accessi a tale membro.
Facendo tale modifica e aggiungendo il seguente metodo:
   shared_ptr<mytype> GetMember_shared() {
     return member;
   }
il degrado di prestazioni si riduce a un fattore 15 (1500%), ma rimane 
sempre alto.
Secondo me lo shared_ptr va usato "cum grano salis".
Nella maggior parte dei casi, il programmatore sa quando verra' 
distrutto l'oggetto di cui si chiede il membro, e quando cessera' di 
essere utile tale membro. Se l'oggetto di cui si chiede il membro verra' 
distrutto dopo il riferimento ottenuto, non ci sono problemi ad usare 
uno dei due metodi di accesso citati, altrimenti occorre prolungare la 
vita dell'oggetto (spostandolo ad uno scope piu' esterno), o fare una 
copia del membro. L'uso degli shared_ptr va relegato al caso (piuttosto 
raro nella mia esperienza) in cui il membro non appartiene a un oggetto, 
ma e' rimpallato tra piu' oggetti.
Comunque, tornando alla domanda originale, non c'e' gran differenza tra 
le due scelte (rendere un puntatore o un reference), anche perche' si 
puo' assegnare il puntatore ad un reference e il reference a un 
puntatore nel seguente modo:
       const mytype * pm = & t.GetMember_ref();
       const mytype & m = * t.GetMember_ptr();
Io comunque consiglio di usare la "GetMember_ptr" se ha senso rendere il 
puntatore nullo, e altrimenti usare "GetMember_ref". Con quest'ultima, 
l'unico comportamento accettabile nel caso non si possa rendere un 
valore valido e' sollevare un'eccezione. La scelta tra "GetMember_ptr" 
che rende un puntatore nullo e "GetMember_ref" che solleva un'eccezione 
dipende dall'eccezionalita' di tale caso. Se capita regolarmente che il 
membro non sia valido, e' meglio rendere il puntatore nullo, se capita 
solo in condizioni veramente anomale e' meglio rendere un riferimento e 
sollevare un'eccezione.
-- 
     Carlo Milanesi
 
http://digilander.libero.it/carlmila

 perche' consente ottimizzazioni impossibili con altri linguaggi.
No, e' da sprovveduti porsi problemi di prestazioni prematuramente. E
non darei affatto per scontato che tutti quelli che usano il C++ lo
facciano solo o anche principalmente per problemi di prestazioni.
Il resto della tua analisi e' plausibile, ma ho qualche osservazione:
l'overhead che indichi mi pare esagerato ed e' comunque credibile solo
in presenza di multithreading; questa e' un'area in cui shared_ptr sta
subendo migliorie proprio in questo periodo e non e' escluso che i
numeri non migliorino con la prossima versione di Boost. In ogni caso
un'ottimizzazione possibile quando non servono thread multipli e'
quella di disabilitare per l'appunto il multithreading.
In altri casi si puo' fare hoisting della dereferenziazione dello
shared_ptr, o accedere allo shared_ptr via reference o a mali estremi
scriversi il proprio smart pointer con le opportune caratteristiche di
prestazioni.
In soldoni: salvo in casi particolari di software che deve essere
architettato per avere particolari prestazioni, a priori sono piu' i
motivi per usare gli shared_ptr che le ragioni per non usarlo.
In pratica e' molto piu' frequente incocciare in problemi di cattiva
gestione della memoria che non in problemi di prestazioni.
 
Ciao,
Nicola Musatti