Newsgroup: it.comp.lang.c++
Titolo: funzioni virtuali a metà !

rompicapo virtuale da un milione di bit:
class A
{
    A()
    {
        ChiSono();
    }
    virtual ChiSono()   {   print("Sono A");    }
}
class B  : public A
{
     virtual ChiSono()   {   print("Sono B");    }
};
B MiaClasse;
// Perché con il VC6 dice "Sono A" e con il BC++ dice giustaente "Sono B"
????

// Perché con il VC6 dice "Sono A" e con il BC++ dice giustaente "Sono B"
Baco del BC++.  A Standard C++: il tipo dinamico di un
oggetto nel suo costruttore e` _esattamente_ il tipo statico
di quel costruttore; cio` significa, in particolare, che se il
"vero" tipo dinamico dell'oggetto e` in realta` _derivato_
da esso, nondimeno le chiamate virtuali, RTTI, ecc ecc,
devono "fermarsi" al tipo che sta venendo costruito.
Ci sono, per questo, ottime ragioni.  Per esempio: tutte
le basi DEVONO venire costruite (in un ordine preciso;
per eredita` singola, dalla piu` "fondamentale" verso la
piu` "derivata") PRIMA dei _membri_ dell'oggetto di cui
sono basi; quindi, ad esempio:
    class C1; class C2;
    class A {
        C1 c1;
        A();
    public:
        virtual void f();
    };
    class B: public A {
        C2 c2;
        B();
    public:
        virtual void f();
    };
l'ordine alla costruzione di B *deve* essere:
    C1(), poi
    A(), poi
    C2(), e infine
    B()
Allora, supponi che A() chiami f(), e B::f()
chiami c2.QualCosa(); se fosse permesso
che la chiamata da A() a f() venisse risolta
a B::f(), questo farebbe si` che la detta
funzione membro di C2 verrebbe chiamata
PRIMA del costruttore del dato oggetto C2,
il che violerebbe tutte le garanzie che il
C++ offre.  E quindi, il C++ Standard impone
invece che la chiamata dal costruttore A(),
ad f(), venga risolta rigorosamente come A::f().
[Considerazioni "speculari" si applicano ai
distruttori].
Ove sia veramente indispensabile che la
inizializzazione di un oggetto di classe A
usi meccanismi virtuali che la portano a
invocare funzioni virtuali di A (con la
possibilita` che esse siano implementate
da oggetti derivati da A), e` dunque
inevitabile una "two-phase construction":
l'inizializzazione deve avvenire in due fasi,
con una specifica chiamata _dopo_ il
vero costruttore (nomi popolari per questa
"fine della inizializzazione" sono Create,
Init, e FinalConstruct).
Considerazioni simili si applicano ai
distruttori (con quella supplementare che
un distruttore NON DEVE lanciare mai
eccezioni), per cui, similmente, si vedono
non di rado funzioni con nomi come Close,
Terminate, o FinalRelease.
Fortunatamente, spesso, l'uso dei
meccanismi virtuali in costruttori e
distruttori (o eccezioni in dtor) non e`
indispensabile, per cui il meccanismo
ctor/dtor puo` bastare.
Alex

Grazie Alex per la tua risposta  egregiamente  esaustiva.
Non pensavo di essere incappato in un problema ansi di questo livello,
comunque ti dirò che questo modo (se pur corretto) mi farà  complicare una
funzionalità di cui invece ero convinto di risolvere in un paio di righe di
codice.
Per amor di verità, devo dirti che la funzione virtuale non è chiamata dal
costruttore, ma da una funzione della classe (l'ho inserita nel costruttore
per *semplificare* il codice).
Grazie per l'help.

ma è ovvio, il BC++ è un compilatore serio, mentre...
Talmente serio, che non si abbassa a banali attivita` come
rispettare lo Standard C++, vero?  Mica come quei volgaroni
della Microsoft, pfui, che in questo caso aderiscono invece
allo standard stesso... che frivolezza!
Alex

Naturalmente scherzo, ma non conosco il VC6
In termini di rispetto per lo Standard C++, ha alcuni
difetti importanti; i due principali sono:
    -- non ammette la covarianza dei valori di ritorno
        da funzioni virtuali (grossa scocciatura!)
    -- non permette la specializzazione parziale di
        template, limitazione rilevante
Sono pero`, fortunatamente, discorsi abbastanza "ai
margini" per la maggior parte dei programmatori C++;
nulla di paragonabili alle notorie deviazioni di BC++ in
questioni fondamentali come i costruttori.  VC++ ha
poi un malnato bug di doppia costruzione se un oggetto
globale e` usato in una successiva reference globale
nello stesso sorgente, situazione, immagino, intricata
davvero, visto che la Microsoft ha il bug in questione
sulla lista dei "bug noti ed ammessi" sin dai tempi della
VC++4.2 e ancora non lo ha riparato... mah!  (Non e`
difficile evitare questa strana e contorta situazione,
naturalmente, se uno ne e` al corrente).  [I bug noti dei
prodotti Microsoft sono documentati sulla "Knowledge
Base", disponibile ad esempio in linea al sito
msdn.microsoft.com
; spesso la documentazione
comprende suggerimenti per work-around e simili].
La libreria standard VC++, di per se`, e` buona (e` della
Dinkumware, di PJ Plauger, segretario del subcomitato
librerie del comitato di standardizzazione C++), ma per
qualche strana ragione la Microsoft non vi ha apportato
le correzioni e migliorie che via via la Dinkumware le
forniva (fortunatamente basta visitare il sito della
Dinkumware per trovare i fix); il limite principale e` che
non e` stata "aggiornata", dai tempi di VC++4.2 in cui
fu inizialmente adottata, per riflettere la rimozione di
varie limitazioni (che VC++4.2 aveva, VC++5 tolse
solo in parte, VC++6 ha totalmente rimosso) relative
ai membri template di classi template (onestamente,
spero che la Dinkumware trovi il tempo di spiegare
quali #define vanno cambiati a questo scopo nei
sorgenti forniti -- sono un po' complicati per darmi
una gran voglia di "reverse-ingegnerarmeli" io!-).
La libreria standard dei compilatori Borland, se non
sbaglio, e` fornita da RogueWave, che, mentre ha
una fama ben meritata per le sue librerie proprietarie,
non ha fatto sinora un lavoro proprio encomiabile su
quelle Standard (non solo le versioni Dinkumware, ma
anche quelle liberamente disponibili in rete sui siti SGI
e ObjectSpace, sono piu` conformanti, e dovrebbe presto
esserlo anche la libg++2.9, per ora disponibile come
"snapshot" [cioe`, _beta_] al sito Cygnus).
Il compilatore C++ per Windows piu` vicino allo Standard
e` forse oggi il piu` recente release di egcs 1.1 (sempre
al sito Cygnus), per quanto anche l'ultimo release del
vecchio Comeau C++ (al sito Comeau; non gratis, ma
mi pare costi solo $50) sia molto, molto buono da questo
punto di vista (il contraltare e` che genera codice cosi`
lento e grosso, che nessuno lo usa "davvero" per farci
un qualsiasi uso professionale... e` forse l'ultimo della
genia dei compilatori C++ che usano C come bersaglio,
approccio che non permette tante ottimizzazioni molto
importanti, che quasi tutti gli altri compilatori realizzano).
Alex

per *semplificare* il codice).
Se e` _in corso_ la costruzione dell'oggetto, qualsiasi siano i
dettagli con cui una funzione virtuale dell'oggetto entra in esecuzione,
deve trattarsi della versione nell'oggetto stesso, non in uno dei
suoi "futuri" discendenti (futuri perche` in quell'istante ancora
non esistono).
Questo, incidentalmente, e` quello che puo` provocare l'errore di
runtime "chiamata a una funzione virtuale pura" (se la funzione
virtuale dell'oggetto e`, appunto, pura, e viene chiamata nel corso
della costruzione dell'oggetto stesso).
Mi rendo conto che avere di mezzo altre funzioni puo` in certi
casi complicare ancora le cose, naturalmente; da un punto di
vista "concettuale" del modello d'oggetto in gioco, pero`, non
cambia le cose.
Se ci spieghi il problema che stai cercando di affrontare, forse
una qualche soluzione ragionevolmente semplice ed elegante
possiamo anche aiutarti a trovarla, chissa`!
Alex

per *semplificare* il codice).
rettifica :
Per amor di verità, devo dirti che la funzione virtuale non è chiamata dal
costruttore, ma da una funzione della classe (l'ho inserita nel costruttore
DELL'ESEMPIO ,per *semplificare* il codice).
A quanto pare però non l'ho affatto semplificato, qundi eccomi ad un esempio
meglio conforme :
class DispositivoSCSIGenerico
{
public:
    DispositivoSCSIGenerico(){};
    virtual GestioneInformazioni(int Codice, void *Dati );
    Inquiry()
    {
        for (Codice=0; Codice<10; Codice++)
        {
            void *Dati =
FunzioneGlobaleCheLeggeIDatiInformatividalDispositivoSCSI();
            GestioneInformazioni(Codice, Dati );
        }
    }
    virtual GestioneInformazioni(int Codice, void *Dati )
    {
        .......//Gestisco i dati per i codici da 1 a 5
    }
};
class CDChanger : public DispositivoSCSIGenerico
{
public:
   CDChanger(){};
    virtual GestioneInformazioni(int Codice, void *Dati )
    {
        //Gestisco i dati per i codici da 1 a 5        (chiamata alla classe
base)
        DispositivoSCSIGenerico::GestioneInformazioni(Codice, Dati );
         .......//Gestisco altri codici dati
  }
};
a questo punto :
CDChanger MyJukeBox();
MyJukeBox.Inquiry();
Questa funzione non chiama la funzione :
MyJukeBox::GestioneInformazioni(int Codice, void *Dati ),
ma invece :
DispositivoSCSIGenerico::GestioneInformazioni(int Codice, void *Dati ),
cosa che mi ha fatto un po'  :-(((
parliamone .....
saluti, Amos.


Amos,
Non riesco a compilare il codice che hai spedito perche' nella classe
"DispositivoSCSIGenerico" la funzione "GestioneInformazioni" e'
dichiarata/definita 2 volte, comunque ho riscritto l'esempio seguendo il tuo
schema.
Con il Visual C++ 6.0 funziona perfettamente, sia scrivendo il codice
all'interno della classe (inline implicito) che scrivendolo al di fuori.
Non ricordo bene, ma mi sembra che tu usassi il Borland C++; forse puoi
provare - a meno che non sia gia' cosi' - a scrivere le funzioni all'esterno
della classe.
Nel caso non funzionasse ancora puoi provare a creare gli oggetti
dinamicamente utilizzando "new" anziche' crearli nello stack. Non ricordo
bene (che memoria!), ma ancora una volta mi sembra che vecchie versioni del
C++ Microsoft non gestissero bene le funzioni virtual se gli oggetti non
erano creati in modo dinamico.
Per spiegarmi meglio, intendo dire di riscrivere il codice in questo modo:
// CDChanger MyJukeBox();
// MyJukeBox.Inquiry();
CDChanger *pMyJukeBox();
pMyJukeBox = new CDChanger();
pMyJukeBox->Inquiry();
// ...
delete pMyJukeBox;
-----
Ciao,
Angelo.

cosa che mi ha fatto un po'  :-(((
Hmmm -- c'e` ancora qualcosa che non va in questo tuo esempio,
che non compila per varie ragioni (tipo quella che qui hai appena
dichiarato una funzione MyJukeBox che non prende argomenti e
torna per valore un CDChanger, e la chiamata Inquiry non puoi
ora applicarla alla funzione).  L'ho pulito un poco, cosi` che compilasse,
e l'output, esattamente come previsto, mostra che viene chiamata
la versione giusta di GestioneInformazioni, naturalmente.  Ecco
l'output:
CDC::GI(0,00000000)
DSG::GI(0,00000000)
CDC::GI(1,00000000)
DSG::GI(1,00000000)
CDC::GI(2,00000000)
DSG::GI(2,00000000)
CDC::GI(3,00000000)
DSG::GI(3,00000000)
CDC::GI(4,00000000)
DSG::GI(4,00000000)
CDC::GI(5,00000000)
DSG::GI(5,00000000)
CDC::GI(6,00000000)
CDC::GI(7,00000000)
CDC::GI(8,00000000)
CDC::GI(9,00000000)
ed ecco il codice ripulito:
#include <iostream>
using namespace std;
void*
FunzioneGlobaleCheLeggeIDatiInformatividalDispositivoSCSI()
{
    return 0;
}
class DispositivoSCSIGenerico
{
public:
    DispositivoSCSIGenerico(){};
    // virtual void GestioneInformazioni(int Codice, void *Dati);
    void Inquiry()
    {
        for(int Codice=0; Codice<10; Codice++)
        {
            void *Dati =
FunzioneGlobaleCheLeggeIDatiInformatividalDispositivoSCSI();
            GestioneInformazioni(Codice, Dati);
        }
    }
    virtual void GestioneInformazioni(int Codice, void *Dati )
    {
        cout<<"DSG::GI("<<Codice<<','<<Dati<<")\n";
        // .......//Gestisco i dati per i codici da 1 a 5
    }
};
class CDChanger : public DispositivoSCSIGenerico
{
public:
   CDChanger(){};
    virtual void GestioneInformazioni(int Codice, void *Dati )
    {
        cout<<"CDC::GI("<<Codice<<','<<Dati<<")\n";
        if(Codice<=5)
        //Gestisco i dati per i codici da 1 a 5 (chiamata alla classe base)
            DispositivoSCSIGenerico::GestioneInformazioni(Codice, Dati);
        //.......//Gestisco altri codici dati
    }
};
// a questo punto :
int main()
{
    CDChanger MyJukeBox;
    MyJukeBox.Inquiry();
    return 0;
}
Qui, dunque, nessuna anomalia -- e infatti non dovrebbe esserci,
perche` la chiamata virtuale avviene DOPO che il costruttore ha
terminato (VC++5, ma questo sicuramente si comporta allo stesso
modo in tutti i compilatori C++ del mondo).
Forse puoi esaminare meglio l'esempio e chiarirci dove si discosta
da cio` che tu invece hai osservato.
Alex

Ragazzi, vi ringrazio per il vs aiuto eccellente;
Chiedo scusa se il codice non era compilabile, l'ho scritto al volo tenendo
presente il codice sorgente reale(molto piů complesso).
Vi ringrazio per avermi ridato la "speranza" nelle funzioni virtuali del
VC++;
e visto che a voi il tutto funziona , farň altre ulteriori verifiche.
Vi faccio sapere.
Grazie 2048.
Amos.

Ragazzi, a quanto pare deve esserci qualcosa di cannato nell'ambiente di
sviluppo, infatti il compilatore non mi richiama neanche i distruttori di
classi derivate come per esempio una classe derivata da CDialog !
mi direte : l'hai definito virtual ?
SI (anche se non è necessario finché non si deriva una classe da essa)
Ho provato a guardare le poche opzioni di compilazione "C++ Language", ma il
tutto è tristemente corretto (se cambio qualcosa rispetto al default , è
peggio)
Qualcuno ha qualche idea in proposito ?

risolto; missilata.
nei dialoghi non modali, va aggiunto :
void miodialogo::OnCancel()
{
 CDialog::OnCancel();
 DestroyWindow();
}
sigh

anzi, meglio ancora ...
void miodialogo::OnCancel() // e penso anche onOk()
{
    CDialog::OnCancel();
    delete this;
}