Newsgroup: it.comp.lang.c++
Titolo: Virtual

 o solo quando vi accede tramite puntatore o rif alla classe madre?
Il late binding interviene solo se l'accesso alla funzione membro è
fatto attraverso puntatori o reference.
EMG

Se dichiaro una funzione virtual, è perchè voglio che, ridefinendola in una
funzione derivata, e accedendovi tramite puntatore o riferimento alla classe
base, venga corettamente chiamata la funzione derivata, e non quella di
base, no?
Il meccanismo del late binding, però, si applica solo in questo caso, o a
ogni tipo di chiamata, ad esempio con puntatori o riferimenti alla classe
derivata, o direttamente con la classe derivata. In pratica, l'indirizzo
della funzione corretta è risolto sempre a run-time per le funzioni virtual,
o solo quando vi accede tramite puntatore o rif alla classe madre?
--
Marco Iannaccone     
marc...@tin.it
ICQ: 18748121           Tolkien
"I've... seen things you people wouldn't believe. Attack ships on fire off
the shoulder of Orion.
I watched C-beams... glitter in the dark near the Tanhauser Gate. All
those... moments will be lost... in time..., like... tears... in... rain."
FAQ it.comp.lang.c: 
http://www.programmazione.it/linguaggi/c/itcomplangc.htm
FAQ it.comp.lang.c++: 
http://www.programmazione.it/linguaggi/c/lang_faq.htm

o solo quando vi accede tramite puntatore o rif alla classe madre?
Il late binding e` utilizzato sempre quando si accede al metodo
tramite puntatore o reference a meno che non si specifichi
l'esatto metodo utilizzando il risolutore di scope:
basePtr->VirtualMet();   // late binding
derived B;
base& b = B;
b.VirtualMet();   // late binding
basePtr->base::VirtualMet();   // early binding
b.base::VirtualMet();   // early binding
Tuttavia il compilatore potrebbe eseguire una analisi molto accurata e
magari scoprire che un certo puntatore punta ad un preciso tipo di
oggetto. In tal caso sarebbe possibile ottimizzare e evitare
l'indirezione. Dubito che almeno i piu` noti compilatori siano cosi`
bravi!
Paolo
-------------------------------------------
  Paolo Marotta
 
http://web.tiscalinet.it/paolo_marotta/
-------------------------------------------

bravi!
Oltretutto non vedo perché dovrebbero sforzarsi tanto per evitare
un'indirezione, che per la maggior parte dei compilatori C++ (almeno tra
quelli efficienti) non è affatto costosa in termini di prestazioni.
Domenico

--
La scelta della giusta funzione da invocare viene effettuata a runtime e
tramite puntatore alla classe base, ma solo per le funzioni virtual.

 ha scritto nel messaggio
news:4fLv5.19618$Sm1.2...@news.infostrada.it...





 alla classe derivata, c'è comunque late binding?
Se il compilatore capisce quale e' la funzione da chiamare 
puo' ottimizzare la chiamata ed eliminare il late binding.
Esempio:
  struct A { virtual void func(); };
  struct B : public A { virtual void func(); };
  A* a = new B;
  a->func();  // puo' chiamare direttamente B::func()
Non basta che il puntatore sia alla classe derivata, perche'
ci potrebbe essere sempre una classe ancora piu' derivata.
Esempio:
  void test( B* b ) {
    b->func();  // non si puo' sapere quale funzione sara' chiamata
  }
  struct C : public B { virtual void func(); };
 
  C* c = new C;
  test( c );
	Mauro

Graize mille!


 quelli efficienti) non è affatto costosa in termini di prestazioni.
In generale hai ragione, ma esistono casi concreti in cui questo
overhead non e' accettabile, ad esempio se la chiamata a membro virtuale
e' effettuata all'interno di un ciclo che viene eseguito un numero
elevato di volte.
Ciao,
Nicola


Come ad esempio:
class RND
{
  public:
    virtual long operator() = 0;
}
class Uniform: public RND
{
  public:
    virtual long operator() { // ... }
};
int main()
{
  Uniform rnd;
  for(size_t i = 0; i < 1000000000; ++i) usa(rnd());
}
?
Mi sono sempre chiesto se un compilatore C++ degno di questo nome
espanda la chiamata rnd() in linea.
Manlio Perillo


Ne dubito molto: oltretutto per definizione una funzione virtual non può
essere inline: il compilatore _non_ conosce a priori la funzione da
chiamare, quindi come fa ad espanderla in linea?
Ciao,
Domenico

chiamare, quindi come fa ad espanderla in linea?
Falso. Una funzione virtual puo' essere inline. Tieni presente che
"inline" e' solo un consiglio per il compilatore che e' pero' libero
di fare quello che vuole (in verita' la parola "inline" ha anche dei
riflessi sul linkage, questo e' pero' un altro problema).
Se il compilatore conosce la classe esatta di un oggetto e' libero
di chiamare direttamente la funzione senza passare per la VMT.
Un caso lampante in cui questo succede e' il distruttore: nel
distruttore di una classe come ultima cosa il compilatore deve
invocare il distruttore delle basi; se questo distruttore e' vuoto
(cosa che succede spesso) dichiararlo inline genera codice molto
piu' efficiente in quanto la chiamata scompare nel nulla.
Quindi, anche se la cosa puo' a prima vista sembrare assurda, nel
caso di distruttori virtuali vuoti di classi base la dichiarazione
inline non solo e' possibile, ma addirittura auspicabile.
L'ottimizzazione della chiamata di funzioni inline virtuali e'
anche possibile quando dal contesto la classe esatta e' derivabile:
    struct A
    {
      virtual void inc(int& x);
    };
    inline void A::inc(int& x)
    { x++; }
    int main()
    {
      A a;
      int x;
      a.inc(x);  // Chiamata di metodo virtuale
      std::cout << x << std::endl;
      return 0;
    }
Con BCC55 in questo caso la chiamata non viene fatta e la
variabile (che non ha nemmeno un'area di memoria associata
ma esiste solamente nel registro ebx) viene modificata
direttamente con
    inc ebx
VC++ addirittura non genera il minimo codice e non alloca
nessun registro ma chiama le routine di output passando
direttamente il risultato (1) che e' stato quindi calcolato
compile time.
Nota che appena rendo la cosa meno evidente con:
    A *a=new A;
    a->inc(x);
entrambi i compilatori effettuano la chiamata attraverso la
VMT (nonostante la classe dell'oggetto puntato da 'a' sia
senza ombra di dubbio 'A')
Andrea

 Manlio Perillo

     }
Non manca l'inizializzazione di x?
EMG

Non manca l'inizializzazione di x?
Vero... nel programma che ho provato a compilare c'era
(spesso scrivo direttamente nel newsreader... poi copio
e incollo in un editor per provare a compilare e a volte
mi dimentico di riportare indietro gli aggiustamenti :( ).
Me ne ero accorto dopo il posting ma avevo pensato che
non fosse il caso di fare un altro posting di correzione...
Cosi' ora ce ne sono due ;)))
Andrea
--
Andrea Griffini, Programmer
http://www.netservers.com/~agriff

>   for(size_t i = 0; i < 1000000000; ++i) usa(rnd());
Immagino che allora fosse usa((*rnd)()) ...
Io non ci vedrei nessun problema ma non ho trovato (per ora)
compilatori che siano cosi' furbi (nemmeno il watcom che sulla
parte di inlining e' abbastanza aggressivo).



Peccato.
Hai provato con il KCC?
Manlio Perillo

quelli efficienti) non è affatto costosa in termini di prestazioni.
Il polimorfismo ha sicuramente un certo overhead, spessissimo non
rilevante... tuttavia l'esatto impatto di quell'overhead dipende da
tanti fattori in particolare anche dalla piattaforma su cui si opera.
Pensa poi al caso di un loop stretto in un punto critico...


espanda la chiamata rnd() in linea.
L'espansione in linea e` un suggerimento, non un obbligo...
Di sicuro pero` in quel caso la chiamata non sarebbe polimorfa
poiche` il tipo dell'oggetto e` sicuramente noto.


senza ombra di dubbio 'A')
Esatto, forse sono stato poco chiaro, ma è proprio questo che intendevo
dire. In questo caso il compilatore (almeno un compilatore tra quelli che
conosco) _non_ ha modo di sapere se tu stia puntando alla classe A o ad una
derivata da A, e sarebbe a mio avviso molto complicato rendere un
compilatore tanto in gamba.
Ciao!
Domenico

chiamare, quindi come fa ad espanderla in linea?
Non e` esatto, in quel contesto il tipo effettivo dell'oggetto e` noto
ed il compilatore non solo puo` (e generalmente lo fa) risolvere
staticamente la chiamata del metodo, ma e` in grado di espanderlo
inline.


    RND* rnd = new Uniform;
In questo caso sarebbe possibile se il compilatore eseguisse
analisi dettagliate (e costose) tramite cui scoprire la classe esatta
dell'oggetto.
Tuttavia e` difficile che cio` avvenga poiche si tratta di algoritmi
non banali e piuttosto costosi.



In effetti chiedevo un pò troppo...
Ma c'è un'altra questione; io ho:
  for(size_t i = 0; i < 1000000000; ++i) usa(rnd());
In questo caso, quale che sia la funzione da chiamare, resta
sempre la stessa.
A quello che ho capito il meccanismo della virtual table non è
altro che un array di puntatori a funzione.
Quindi per chiamare la 'giusta' funzione si deve calcolare
l'offset del puntatore e quindi chiamare la funzione.
Nel mio caso è possibile che il compilatore 'memorizzi'
il puntatore a funzione senza ricalcolare l'offset ogni volta?
C'è un modo perchè faccia da me stesso questa cosa?
Manlio Perillo


Curiostità:
class a {
	virtual int func(int x) { /* .... */ };
};
class b: public a {
	virtual int func(int x) { /* ..... */ };
};
int main() {
	a* ptr;
	/* qualche istruzione */
	for (int i=0; i<1000; ++i) {
		a->func(i);
	}
}
all'interno del ciclo for il compilatore riesce ad ottimizzare in modo
da cercare una sola volta nella virtual table l'indirizzo della
funzione giusta e usare quello per tutte le chiamate fatte li dentro?

C'è un modo perchè faccia da me stesso questa cosa?
Prova a generare il codice assembly e verificare cosa
succede, non penso di sbagliarmi (almeno spero)...
Appena ho 2 minuti faccio un test, il fallimento del test
non sarebbe una risposta certa, ma il successo si!


funzione giusta e usare quello per tutte le chiamate fatte li dentro?
Non ho ancora provato, ma ne sono convinto, l'ottimizzazione
rientra tra quelle classiche (invariant code detection).



Intendevo dire sempre in C++.
Posso cioè memorizzare il puntatore a funzione 'giusto'
ed usare quello per le successive chiamate?
Manlio Perillo