Newsgroup: it.comp.lang.c++
Titolo: classie derivate

se:
int k;
class base
{
public:
    virtual int f( void ) { return 1; }
    base( ) { k = f( ); }
};
class derivata : public base
{
public:
    virtual int f( void ) { return 2; }
    derivata( ) : base( ) { }
};
int main()
{
    derivata d;
    cout << k;
    return 0;
}
k assume valore 1;
io invece vorrei che venisse chiamato il metodo f( ) ridefinito in classe
derivata.( k = 2 )
come posso fare?
Alberto


Cosě come č non si puň fare.
Stai chiamando una funzione virtuale nel costruttore.
Ecco una possibile soluzione:
#include <iostream>
int k;
class base
{
   protected:
      void init() { k = f(); }
   public:
      virtual int f(void) { return 1; }
      base() { init(); }
};
class derivata : public base
{
   public:
      virtual int f(void) { return 2; }
      derivata()
      : base() { init(); }
};
int main()
{
   derivata d;
   std::cout << k;
}
Manlio Perillo

come posso fare?
Nel costruttore di derivata devi chiamare la funzione f() definita in
derivata
derivata() { k=f(); } (cosě dovrebbe andare);
Ciao
Bomenico Barra


Ma in questo modo k non viene scritta per 2 volte, per poi tenere solo
l'ultimo assegnamento?
Se init fosse piů grande vi sarebbe un certo spreco.
Grazie
 Alberto.

 derivata.( k = 2 )
Se non sbaglio all'interno del costruttore non viene utilizzato il
binding dinamico, e quindi non viene richiamata la funzione definita
nella classe derivata... del resto e' giusto: se la f nella classe
derivata accedesse ad un campo definito nella classe derivata in quel
momento li' non sarebbe inizializzato tale campo.
ciao
	Lorenzo
-- 
+-----------------------------------------------------+
|  Lorenzo Bettini          ICQ# lbetto, 16080134     |
|  PhD student in Computer Science                    |
|  Florence - Italy         (Linux User # 158233)     |
|  Home Page        : 
http://w3.newnet.it/bettini
     |
|           
http://infostud.dsi.unifi.it/~bettini
     |
|  Mail Home        : 
lorenzo...@penteres.it
     |
|  Mail University  : 
bet...@dsi.unifi.it
            |
|  
http://www.mokabyte.it
       Java on line journal  |
|  
http://music.dsi.unifi.it
         XKlaim language  |
|  
http://www.gnu.org/software/java2html
 & cpp2html   |
+-----------------------------------------------------+

 }
Ma in questo modo viene comunque chiamata anche la init() della classe
base e questo potrebbe essere un problema.
EMG

 derivata() { k=f(); } (così dovrebbe andare);
Anche così viene comunque chiamato anche il costruttore della classe
base e quindi anche la funzione base::f(). E questo, come ho già detto,
potrebbe rappresentare un problema.
EMG

potrebbe rappresentare un problema.
D'accordo, ma se una classe derivata non funziona bene col costruttore della
classe base, allora c'è qualche *serio* problema di progettazione, non
trovi?
Ciao
Domenico Barra

 EMG
In effetti direi che l'unico modo per ottenere di chiamare solo la f
corretta e' di utilizzare un'inizializzazione esplicita (ovvero chiamare
f direttamente).
Ciao,
Nicola

come posso fare?
ad esempio dividendo l'inizializzazione in 2 fasi!
Paolo

Se init fosse più grande vi sarebbe un certo spreco.
Basta tirare fuori la init dal costruttore, naturalmente il
codice cliente dovrebbe ricordarsi di chiamare la init subito dopo
la costruzione dell'istanza.
Paolo


Di niente
Manlio Perillo

 trovi?
Si, ma mi sembra che l'amico voleva venisse chiamata soltanto la
funzione f() relativa al tipo dinamico dell'oggetto istanziato e non
anche quella della classe base.
EMG

 ad esempio dividendo l'inizializzazione in 2 fasi!
Esempio, please. :-)
EMG

 la costruzione dell'istanza.
Ah, ecco. Non mi piace, ma se è l'unico modo...
EMG


Dipende da che cosa fa init.
Manlio Perillo

L'alternativa e' passare a Delphi, che supporta i costruttori virtuali
(per compatibilita' lo fa anche il bcc, ma solo in casi specifici).
Ciao,
Nicola

inizializzata).
Non si tratta di un punto che dipende dall'implementazione: quale
deve essere il comportamento in questo caso e' definito chiaramente.
Il problema e' logico: durante l'esecuzione del costruttore della
classe "base" l'oggetto non e' ancora di tipo "derivato"; chiamare
funzioni di un oggetto non costruito sarebbe un controsenso.
Un comportamento simile si ha anche durante la distruzione della
base (chiamare un metodo di un oggetto che non e' piu' un "derivato"
essendo gia' stata distrutta quella parte non sarebbe corretto).
Le cose vanno diversamente in altri linguaggi OO.
Andrea
--
Andrea Griffini, Programmer
http://www.netservers.com/~agriff


init(   )  modifica il valore di un oggetto extern,  tra l' altro
utilizzando il valore di ritorno di una funzione virtuale.
Alberto

 Dipende da che cosa fa init.
Chiaro. Per questo ho scritto "potrebbe". Diciamo che mi sarebbe
piaciuta una soluzione definitiva ma a quanto pare non esiste.
EMG

costruttori sono risolte staticamente.
Scusa, ma VTABLE non e' di un'istanza di un oggtto ( essendo condivisa da tutte
le istanze ). Per cui non vedo perche' non possa essere inizializzata
staticamente. Anche perche' cosi' facendo significa che ( in linea di
principio ) non posso chiamare nessun membro virtuale ( classe base o
derivata che sia .... ) in un costruttore. Mi sembra una limitazione pesante

Esempio, please. :-)
Spiacente per il ritardo della risposta, ma o mi inventate le giornate
di 48 ore oppure aspettate che inventino un metodo per clonare
rapidamente una persona anche dal punto di vista delle sue esperienze!
:-o
Intendevo proprio cio` che ha affermato Nicola Musatti in un suo post
di questo thread... chiamare la Init esplicitamente, imponendo
(come contratto) al codice cliente l'invocazione di Init() una sola
volta dopo la costruzione dell'oggetto e prima di invocare altri
metodi.
T a(param1, param2, ... param_n);
a.Init();
Paolo

essendo gia' stata distrutta quella parte non sarebbe corretto).
Questo e` invece del tutto errato, la chiamata ad un metodo virtuale
nel distruttore viene risolta normalmente anche perche` i distruttori
sono chiamati in ordine inverso (prima quello di classe derivata,
poi quello di classe base).
Paolo

L'alternativa e' passare a Delphi, che supporta i costruttori virtuali
Sarebbe interessate sapere esattamente come funziona e fare un
paragone con le scelte del C++... ovviamente se non vi sembra troppo
OT (a me onestamente non sembra).
Paolo

Conosco Delphi solo per sentito dire, per cui potrei scrivere
stupidaggini. Prima di tutto in Delphi le istanze delle classi sono
sempre allocate dinamicamente. In secondo luogo ogni membro e'
inizializzato a zero (ed i puntatori a NIL), quindi si puo' sempre far
conto su un valore di partenza consistente.
Delphi poi prevede i cosiddetti class reference, ovvero delle variabili
che rappresentano il tipo di una classe ed alle quali si puo' assegnare
anche il tipo di una sotto-classe della classe di partenza (analogamente
a come si puo' fare per le istanze in C++ con puntatori/reference).
E' possibile quindi creare un'istanza di una classe a partire da un
class reference e la classe effettivamente istanziata e' determinata
dinamicamente.
Cose analoghe si possono probabilmente fare anche in Java.
Ciao,
Nicola

 Domenico Barra
Effettivamente hai ragione : ciò che volevo far fare al costruttore non è
necessario e forse è meglio farlo fare ad  un altro metodo.
Certo che non è facile progettare efficacemente gerarchie di classi.
Voi che approccio utilizzate, se non sono indiscreto?
Alberto.

OT (a me onestamente non sembra).
Vi assicuro che Object Pascal, dal punto di vista della programmazione
object oriented fa rimpiangere il C++.
A me non piace, onestamente, il fatto di dover forzare la chiamata al
costruttore.
Es.:
var MiaStringList : TStringList ;
begin
  MiaStringList := TStringList.Create ;
  ...
  MiaStringList.Free ;  
end ;
Dai, che orrore !!! ;)
--
Andrea Laforgia (ICQ 87230455)
------------------------------ 
#exclude <windows.h>

   MiaStringList.Free ;
Questo si che mi turba!
Ciao,
Nicola

Questo si che mi turba!
eh gia' ! ;)


poi quello di classe base).
Acqua... Durante la distruzione della base eventuali chiamate a metodi
virtuali sono risolte "staticamente" per la stessa identica ragione:
infatti quando viene chiamato il distruttore della base il distruttore
della derivata e' gia' stato eseguito; chiamare un metodo di una
classe quando l'oggetto e' gia' stato distrutto sarebbe un
controsenso. Proprio per il fatto che costruzione e distruzione
avvengono in ordine inverso il comportamento e' identico:
  #include <iostream>
  class X
  {
    public:
      virtual ~X()
      { f(); }
 
      virtual void f()
      { std::cout << "X::f()\n"; }
  };
  class Y : public X
  {
    public:
      virtual void f()
      { std::cout << "Y::f()\n"; }
  };
  int main()
  {
    Y y;
    return 0;
  }
(un febbricitante) Andrea


 poi quello di classe base).
Secondo me ha ragione Andrea. La situazione è perfettamente simmetrica a
quella della costruzione.
EMG



No, l'oggetto e` del tutto definito al momento in cui viene eseguito
il suo distruttore (fino all'ultima istruzione dello stesso). Tant'e`
che i distruttori possono (e spesso devono) essere virtuali.
Mi sorge il dubbio di aver frainteso... forse intendevate dire che
la chiamata (dal distruttore di c. base) di un metodo virtuale
ridefinito nella classe derivata viene risolto in favore del metodo
visibile nello scope di classe base.
In tal caso credo sia giusto... (anche perche` l'alternativa non
sarebbe corretta).
Paolo

virtuali sono risolte "staticamente" per la stessa identica ragione:
Si in effetti avevo frainteso, pensavo ti riferissi all'esecuzione
del distruttore di classe derivata.
Paolo

metodo corretto.
Il metodo "corretto" da chiamare e' quello della base. Punto.
Questa e' la filosofia del C++ e non un problema di implementazione.
Non importa se la chiamata avviene staticamente o tramite un late
binding. La versione della classe derivata *NON DEVE* essere
chiamata perche' significherebbe chiamare un metodo prima di
aver costruito l'oggetto.
Questo costa anche qualche fatica al compilatore ma e' una delle
richieste del linguaggio C++. Per capire che anche nel caso che
venga utilizzato il late binding non viene chiamata la derivata
considera questo esempio:
    #include <iostream>
 
    class Base
    {
      public:
	Base()
	{ f(); }
 
	virtual void f()
	{ g(); }
 
	virtual void g()
	{ std::cout << "Base::g()\n"; }
    };
 
    class Derivata : public Base
    {
      public:
	virtual void g()
	{ std::cout << "Derivata::g()\n"; }
    };
 
    int main()
    {
      Derivata d; // Verra' chiamata Base::g
      d.f();      // Verra' chiamata Derivata::g
      return 0;
    }
Ovviamente il codice di Base::f() utilizza il late binding
per la chiamata di g (nota che ad esempio Base::f potrebbe
anche essere definita in una diversa compilation unit) e
questo significa che il compilatore deve:
  1) Fare puntare l'oggetto alla VMT di Base
  2) Eseguire il corpo del costruttore di Base
  3) Fare puntare l'oggetto alla VMT di Derivata
  4) Eseguire il corpo del costruttore di Derivata
Questo comportamento e' richiesto dal linguaggio. Non si
tratta di un "errore di implementazione" ma di una
decisione ragionata.
Sicuramente scrivere codice che si basa su questo comportamento
per fare quello che si vuole e' poco consigliabile visto che
rende la cosa meno evidente. Questo comportamento e' pero'
ben definito dallo standard C++ e non credo che esistano
compilatori che violano queste regole di base.
Questo "cambio di tipo" dell'oggetto durante la costruzione
e' semplicemente accennato in "The C++ Programming Language"
(terza edizione) in 15.4.3. Una discussione dello specifico
problema e' pero' presente nelle FAQ di Marshall Cline; nella
versione che ho io di C++FAQ-lite (free) la spiegazione e'
la prima della sezione 23 ("Inheritance - What your mother never
told you") mentre nel libro "C++ FAQs" sono le FAQ 13 e 14 del
capitolo 20 ("Constructors and Destructors").
Sono anche sicuro che si tratta di un comportamento descritto
dettagliatamente dal documento di definizione dello standard.
Sia il sistema scelto dal C++ che il sistema usato da Java
(complementare) hanno pregi e difetti; l'importante e' comprendere
bene la differenza fra i due approcci.
Se tu stavi dicendo esattamente la stessa cosa mi scuso ma
non lo avevo proprio capito... i "non e' garantita" e i
"generalmente" che hai scritto lasciavano qualche incertezza
su quale deve essere il comportamento di un compilatore C++ in
quel caso (incertezza che in quel caso non esiste affatto).


Dai, che orrore !!! ;)
E io che pensavo (speravo) che questa "simpatica" feature l'avessere
cambiata nel passaggio da Turbo Pascal 7 ad Object Pascal :-(((

 visibile nello scope di classe base.
Esatto. E' la situazione perfettamente speculare rispetto a quella di
cui parlavamo nel thread nel quale l'amico avrebbe voluto chiamare una
funzione virtuale nel costruttore dela classe base ma non otteneva i
risultati sperati.
EMG

binding. La versione della classe derivata *NON DEVE* essere
il "generalmente" era riferito al fatto che quello in esecuzione
potrebbe essere proprio il costruttore di classe derivata. Il mio
discorso era riferito all'esecuzione di un costruttore qualsiasi, non
solo quello di classe base.
Se quello in esecuzione e` effettivamente il costruttore dell'oggetto
finale che si vuole ottenere, il metodo chiamato sara` quello che ci
si aspetta normalmente... da qui` il "generalmente".
Paolo