Newsgroup: it.comp.lang.c++
Titolo: Ereditarietà virtuale

Salve a tutti,
il compilatore Dev-C++ mi restituisce il seguente errore "ambigous
declaration of virtual pointer":
#include <iostream>
using namespace std;
class Base {
     // codice
};
class estensione1: virtual public Base {};
class estensione2: virtual public Base {};
class final: public estensione1, public estensione2 {};
 // NB: Volutamente ho tralasciato le dichiarazioni di funzioni ed
attributi membro nelle classi.
In fase di compilazione ricado nell'errore sopra citato. Per adesso ho
risolto il problema con il risolutore di scope, ma ovviamente alcuni
"imprevisti" sono ancora presenti.
Qualche idea a riguardo?
-- 
La classifica dei prodotti MS:
C#, la migliore barzelletta di Bill Gates.
Visual C++, il meno standard dei compilatori C++.
J++, la copiatura più riuscita.
Windows, il più instabile.
Visual Basic, un esempio di prodotto decente della MS - forse l'unico -.
OutlookExpress, scandali su scandali.
La classifica degli "amici" coglioni:
Stefano Zanchi, il miglior lamer del mondo.
 

 attributi membro nelle classi.
La parte fondamentale per capire... :)
--
Ivan Molella
__LINUX__ the choice of the __GNU__ generation
icq# 97960484

attributi membro nelle classi.
Il codice che hai presentato mi sembra corretto e BCC55 non ha nessun
problema a compilare e fare il link del seguente programma:
  class Base {};
  class estensione1: virtual public Base {};
  class estensione2: virtual public Base {};
  class final: public estensione1, public estensione2 {};
  int main()
  {
    return 0;
  }
Sei sicuro che l'errore non dipenda da linee che non hai mostrato ?
(non ho il Dev-C++ e non posso verificare).
Andrea
--
Andrea Griffini, Programmer
http://www.netservers.com/~agriff

 La parte fondamentale per capire... :)
Non in questo caso. Tali classi dovevano gestire un progetto calcolatrice
di base che contenesse le operazioni elementari, potenza e radice quadrata.
Cmq rimane il fatto che non riesco a capire per quale motivo mi viene dato
errore. Io non ho usato nessun virtual pointer, escluso quello aggiunto dal
compilatore per l'ereditarietà virtuale.


(non ho il Dev-C++ e non posso verificare).
Non ti preoccupare, mi basta quello che hai detto.
Grazie.


   class estensione1: virtual public Base {};
Scusa, mi spieghi il significato della keyword "virtual" in questo contesto
?
Massimo


wrote:

Il discorso e' un po' complesso (almeno per me) ... provo a
dire quello che ho compreso confidando in eventuali correzioni
nel caso ci siano errori.
Quando tu hai un oggetto derivato da una base in maniera
"non-virtual" (quella normale) una istanza dell'oggetto
"possiede" una istanza della base. In altri termini l'oggetto
"base" e' un sotto-oggetto dell'oggetto derivato: un po' come
se esistesse un membro di tipo "Base" nel derivato.
  class Base
  {
    ...
  };
  class Derived // : Base
  {
    Base base;
    ...
  };
Questo membro e' pero' speciale per una serie di motivi...
ad esempio quando vuoi accedere a membri public o protected
di base il compilatore aggiunge automaticamente "base.".
Un'altra differenza dell'inheritance rispetto al semplice
contenimento e che un puntatore a una istanza di Derived
viene convertito automaticamente in puntatore al
sotto-oggetto base quando richiesto dal contesto.
Quello che pero' ci interessa in particolare in questa
discussione e' che ogni singola istanza di Derived contiene
una "propria" istanza di Base ad uso esclusivo.
Quando invece derivi in modo virtuale e' un po' come se
nell'istanza dell'oggetto Derived esistesse un
*reference* associato ad una istance di Base; in C++:
  class Base
  {
    ...
  };
  class Derived // : Base
  {
    Base& base;
  };
Nel caso che ci sia inheritance virtuale esiste quindi
la possibilita' che piu' di una istanza di oggetti
derivati puntino ad una stessa istanza di Base.
In pratica il sotto-oggetto di tipo Base potrebbe non
essere esclusivamente di Derived ma potrebbe essere
condiviso con altri.
Questo accade quando una classe e' derivata da due
classi che a loro volta sono derivate virtualmente
da una stessa base.
Provando a disegnare uno schemino:
Caso 1: Inheritance "normale"
    Base          Base
      |             |
   Derived1      Derived2
      |_____   _____|
            | |
         SubDerived
Caso 2: Inheritance "virtual" fra Derived1 e Base e
        fra Derived2 e Base.
            Base
       ______|______
      |             |
  Derived1       Derived2
      |_____   _____|
            | |
         SubDerived
Se consideri il seguente programma puoi notare come
nel caso di inheritance per ogni oggetto SubDerived
sono presenti due distinti sotto-oggetti Base (caso 1)
mentre in caso di inheritance virtuale questo oggetto
e' unico (caso 2).
/////////////////////////////////////////////////
  #include <iostream>
  class Base
  {
    public:
      Base()  { std::cout << "Base()\n"; }
      ~Base() { std::cout << "~Base()\n"; }
  };
  class Derived1 : virtual public Base
  {
    public:
      Derived1()  { std::cout << "Derived1()\n"; }
      ~Derived1() { std::cout << "~Derived1()\n"; }
  };
  class Derived2 : virtual public Base
  {
    public:
      Derived2()  { std::cout << "Derived2()\n"; }
      ~Derived2() { std::cout << "~Derived2()\n"; }
  };
  class SubDerived : public Derived1,Derived2
  {
    public:
      SubDerived()  { std::cout << "SubDerived()\n"; }
      ~SubDerived() { std::cout << "~SubDerived()\n"; }
  };
  int main()
  {
    SubDerived sd;
    return 0;
  }
/////////////////////////////////////////////////
Output:
  Base()        // Base in comune fra Derived1 e Derived2
  Derived1()    // Sub-oggetto Derived1 di SubDerived
  Derived2()    // Sub-oggetto Derived2 di SubDerived
  SubDerived()
  ~SubDerived()
  ~Derived2()
  ~Derived1()
  ~Base()
Output rimuovendo "virtual" dalle definizioni di Derived1
e di Derived2 (inheritance normale).
  Base()        // Sub-oggetto Base per Derived1
  Derived1()    // Sub-oggetto Derived1 di SubDerived
  Base()        // Sub-oggetto Base per Derived2
  Derived2()    // Sub-oggetto Derived2 di SubDerived
  SubDerived()
  ~SubDerived()
  ~Derived2()
  ~Base()
  ~Derived1()
  ~Base()
----
Personalmente non sono un grande tifoso dell'inheritance multipla
(tranne il caso di uso del concetto di interfaccia) e non mi sono
mai trovato ad affrontare un problema reale in cui ho sentito la
necessita' di inheritance virtuale. Ho pero' iniziato a programmare
ad oggetti con il "Borland Pascal with Objects" dove l'inheritance
multipla non e' presente e quindi mi viene naturale pensare a
soluzioni che non la utilizzano.
L'inheritance multipla e virtuale complica un po' le regole e a
volte e' apparentemente strano il comportamento del compilatore
in alcuni frangenti come la risoluzione degli overload.
Si tratta di qualcosa di molto potente e molto complesso. Nella
comunita' C++ c'e' anche chi pensa che la multiple inheritance
sia semplicemente una pessima idea... qualcosa che e' al posto
giusto solo nel codice delle ditte concorrenti.
HTH


 giusto solo nel codice delle ditte concorrenti.
Grazie della spiegazione estremamente chiara, sapevo dell'ereditarietą
multipla, ma quella virtuale proprio non l'avevo mai sentita.
Massimo

 e' unico (caso 2).
Ritengo valga la pena di riportare anche il seguente esempio che mostra
un possibile problema nel caso di inheritance multipla non virtuale che
rende il codice non compilabile a causa dell'ambiguità derivante dalla
presenza delle due basi:
#include <iostream>
class Base
{
  public:
    Base()  { std::cout << "Base()\n"; }
    ~Base() { std::cout << "~Base()\n"; }
    int n;
};
class Derived1 : public Base
{
  public:
    Derived1()  { std::cout << "Derived1()\n"; }
    ~Derived1() { std::cout << "~Derived1()\n"; }
};
class Derived2 : public Base
{
  public:
    Derived2()  { std::cout << "Derived2()\n"; }
    ~Derived2() { std::cout << "~Derived2()\n"; }
};
class SubDerived : public Derived1,Derived2
{
  public:
    SubDerived()  { std::cout << "SubDerived()\n"; }
    ~SubDerived() { std::cout << "~SubDerived()\n"; }
};
int main()
{
  SubDerived sd;
  sd.n = 123; // Member is ambiguous: 'Base::n' and 'Base::n' in
function main()
  return 0;
}
EMG

Ringrazio Andrea per la spiegazione che è stata di aiuto anche a me.
Quindi in caso di eredità virtuale si copia solo un puntatore agil elementi
della classe base nella derivata?
Se per caso avessi una situazione simile a:
class Base {}
class estensione1: virtual public Base {}
class estensione2: public Base {}
class estensione3. public estensione1, public estensione2 {}
Cosa viene fuori?


Cosa viene fuori?
estensione2 richiede una propria copia di Base ad uso esclusivo.
Quindi esisteranno due istanze di Base come sotto-oggetti
di estensione3 (questa era la mia idea e ho verificato che il
comportamento di BCC55 in questo caso e' concorde).
Se modifichiamo in
 Base
 estensione1: virtual public Base
 estensione2: virtual public Base
 estensione3: public Base
 estensione4: public estensione1, estensione2, estensione3
Allora esistono per ogni istanza di 'estensione4' due sotto-oggetti
di tipo Base: uno ad uso esclusivo di estensione 3 e l'altro in
comune fra estensione1 e estensione2.
Anche questa era una mia ipotesi che ho verificato con il comportameto
di BCC55: non pretende cioe' di essere una affermazione assoluta...
non ho letto molto su questa parte del C++.
Sicuramente se dovessi prendere in carico del codice scritto in
questo modo investirei un po' di tempo per cercare l'autore e
prenderlo leggermente a schiaffi.


Ringrazio nuovamente Andrea per l'ulteriore spiegazione.
Cmq mi rimane un dubbio:
class Base {
        public:
                int a;
                Base(int variabile): a(variabile) {}
};
class estensione1: virtual public Base {
        public:
                estensione1(): Base(10) {} 
};
class estensione2. virtual public Base {
        public:
                estensione2(): Base(20) {} 
};
int main(void) {
      estensione1 oggetto;
      estensione2 oggetto;
      using namespace std;
      cout << oggetto.a << endl;
      cout << oggetto.a << endl;
};
ouput:
 10
 20
Ma se entrambi possiedo la stessa istanza di base non dovrebbe essere
stampato il valore 20 in entrambi i casi, essendo stata l'ultima modifica
fatta dal costruttore di estensione2?


Questo è vero, Enrico, ma molte volte ho visto che alcuni usavano
specificare con il risolutore di scope se volevano la versione ereditata
dalla derived1 o dalla derived2 ed il problema si risolve elegantemente.
Quindi penso che questa sia una valida alternativa all'ìereditarietà
virtuale che in molti casi può essere limitante.


fatta dal costruttore di estensione2?
In caso di inheritance virtuale l'istanza del sotto-oggetto base
viene condivisa fra eventuali basi di un altro oggetto derivato.
Oggetti derivati distinti hanno ovviamente sotto-oggetti base
distinti in ogni caso.
Considera questo caso:
  class Base { }
  class Derived1 : virtual public Base { }
  class Derived2 : virtual public Base { }
  class Derived3 : virtual public Base { }
  class SubDerived : public Derived1,Derived2
  {
    Derived3 d3;
  }
Quando viene istanziato un oggetto SubDerived vengono creati
*due* oggetti Base: uno e' quello associato a SubDerived::d3
e uno e' quello in comune fra le due basi Derived1 e Derived2.
Spero di non aver aumentato la tua confusione sull'argomento


virtuale che in molti casi può essere limitante.
L'ereditarieta' virtuale non e' un sistema per evitare di usare il
risolutore di scope. In alcuni casi (secondo me rari) non e' logico
che esistano due istanze del sotto-oggetto base...
Considera ad esempio:
 - Automezzo (fra i membri ha la velocita' attuale)
 - Casa mobile (derivata da automezzo)
 - Chiosco mobile (derivato da automezzo)
 - Casa mobile con annessa rivendita (derivato da casa mobile
      e da chiosco mobile)
E' illogico che una casa mobile con annessa rivendita abbia due
velocita' distinte; per sistemare le cose basta utilizzare
inheritance virtuale verso automezzo. Senza inheritance virtuale
si e' obbligati a rompere la simmetria e a dire ad esempio che
una casa mobile con annessa rivendita deriva da chiosco mobile
e da casa (non mobile); si tratta pero' di una forzatura che non
permette di utilizzare la sostituibilita' di una casa mobile con
annessa rivendita in luogo di una casa mobile.
Se ha senso usare il risolutore di scope per l'ereditarieta' multipla
allora non ha senso utilizzare inheritance virtuale; quale dei due
ha senso discende pero' dal significato... non si tratta di
alternative per risolvere uno stesso problema sintattico.


  20
Errata corrige: ovviamente volevi scrivere "estensione1 oggetto1;" e
"estensione2 oggetto2;"...
in questo caso crei due istanze diverse e quindi, correttamente, hai DUE
istanze diverse della classe "Base".
A quanto ho capito la stessa istanza di classe base si condivide
nell'ordine di inizializzazione di un solo oggetto ed ha utilita', secondo
me, se hai un terzo livello di ereditarieta'... chiedo lumi! :-)
Comunque aggiungo qualcosa al tuo esempio x proporre un altro bel quesito,
IMHO:
-------------------
            Base
             |
             |(ereditarieta' virtuale)
             |
       ______|______
      |             |
  estensione1    estensione2
      |_____   _____|
            | |
            | |
            | |(ereditarieta' normale)
            | |
        SubEstensione
class Base {
        public:
                int a;
                Base(int variabile): a(variabile) {cout <<
"Base(variabile)"<<endl;}
                Base():  a(0){cout << "Base()"<<endl;}
};
class estensione1: virtual public Base {
        public:
                estensione1(): Base(20) {cout << "estensione1()" << endl;}
};
class estensione2: virtual public Base {
        public:
                estensione2(): Base(10) {cout << "estensione2()" << endl;}
};
class SubEstensione: public estensione1, public estensione2{
        public:
                SubEstensione() {cout << "SubEstensione2()" << endl;}
};
int main() {
   estensione1 oggetto1;
   estensione2 oggetto2;
   SubEstensione oggetto3;
   using namespace std;
   cout << oggetto1.a << endl;
   cout << oggetto2.a << endl;
   cout << oggetto3.a << endl;
   return 0;
}
-------------------
output:
Base(variabile)
estensione1()       <--  costruisco oggetto1
Base(variabile)
estensione2()       <--  costruisco oggetto2
Base()
estensione1()
estensione2()       <--  costruisco oggetto3: attenzione è stato usato il
costruttore di default!!!!!!!!!
SubEstensione2()
20                  <--  output oggetto1: ok
10                  <--  output oggetto2: ok
0                   <--  output oggetto3: costruito tramite Base():
perche'?????
chiedo spiegazioni su questo comportamento: in pratica quando deve
costruire oggetto3 si trova di fronte ad una incongruenza e allora usa
Base()????
penso che questo caso sia di design sbagliato, ma e' interessante capire
come funziona il tutto...
spero di non aver detto troppe cavolate!
--
Ciao by Beppe

 spero di non aver detto troppe cavolate!
La spiegazione a cio' e' che  Stroustrup ha deciso di gestire tale
situazione, imponendo che (come da libro) "il costruttore di una classe base
virtuale viene invocato (implicitamente o esplicitamente) dal costruttore per
l'oggetto completo".
Quindi nel caso specifico sara' il costruttore di SubEstensione che dovra'
preoccuparsi di gestire l'inizializzazione di Base.


 l'oggetto completo".
Non ci sono alternative, perche' la scelta dell'una o l'altra classe
intermedia sarebbe comunque arbitraria. Certamente le due classi sono
scritte per essere "combinabili", ma nessuna da sola puo' sapere come.
Ciao,
Nicola

 giusto solo nel codice delle ditte concorrenti.
Personalmente ho trovato almeno un caso in cui l'ereditarieta' multipla
virtuale mi ha portato a risolvere un problema concreto in modo a me
pare soddisfacente.
Si trattava di combinare una gerarchia di tipo "caso generale - caso
particolare" con una gerarchia di tipo "semplice composito". Qualcosa
come:
class TipoGenerico {};
class TipoSpecificoA : public TipoGenerico {};
class TipoSpecificoA : public TipoGenerico {};
e 
class SempliceGenerico {};
class CompositoGenerico : public SempliceGenerico {};
Il risultato e' stato sostanzialmente:
class SempliceGenerico {};
class CompositoGenerico : public virtual SempliceGenerico {};
class SempliceSpecificoA : public virtual SempliceGenerico {};
class CompositoSpecificoA : public SempliceSpecificoA, 
	public CompositoGenerico {}
E analogamente per B (in realta' i tipi specifici erano quattro). Questo
si e' reso necessario per diversi motivi fondamentali: in primo luogo in
contesti diversi della mia applicazione erano richieste *tutte* le
interfacce cosi' definite, ad esempio per poter rappresentare
congiuntamente oggetti dei tipi SpecificoA e SpecificoB non potevo che
ricorrere ad un tipo Generico, Semplice o Composito a seconda dei casi.
Secondo, la gerarchia semplice/composito era ricorsiva, ovvero vi era un
primo livello composito composto da oggetti di tipo Semplice ed un
secondo livello composito composto da oggetti di tipo composito del
primo livello.
In ultimo sia l'interfaccia generica comune che le diverse interfacce
specifiche erano notevolmente complesse, con decine di metodi ciascuna,
cosicche' e' risultato indispensabile utilizzare l'ereditarieta' anche
per fattorizzare l'implementazione; sebbene in generale questo sia
sconsigliabile, nel caso specifico l'accoppiamento era insito nel
dominio rappresentato, e quindi la condivisione dell'implementazione non
avrebbe portato effetti particolarmente negativi.
Ciao,
Nicola

come:
...
Devo pensare un po' alla soluzione ma a sensazione mi sembra pulita.
In altri casi ho visto semplicemente identificare un oggetto
semplice con un oggetto composito privo di sotto-oggetti. Se non mi
sbaglio anche Gamma & C propongono qualcosa di simile.


 sbaglio anche Gamma & C propongono qualcosa di simile.
In realta' nell'esempio non ho detto tutto (per forza ci stanno dietro
alcune migliaia di linee di codice :-). Nel mio caso la gerarchia
semplice/composito non modella in modo specifico la composizione, quanto
il fatto che l'elemento composito possiede in aggiunta alle proprieta'
di quello semplice alcune proprieta' specifiche, ad esempio entrambi
hanno il metodo getValore(), ma solo composito ha il metodo getMedia().
Le proprieta' strutturali relative alla composizione le ho modellate
tramite template perche' allo stesso livello gli elementi compositi
hanno struttura analoga; riprendendo l'esempio:
class SempliceGenerico {};
class CompositoGenerico : public virtual SempliceGenerico {};
class SempliceSpecificoA : public virtual SempliceGenerico {};
class CompositoSpecificoA : public SempliceSpecificoA, 
        public CompositoGenerico {}
template <typename Base, typename Elemento>
class Livello1 : public Base {}
class Livello1SpecificoA : 
	public Livello1<CompositoSpecificoA,SempliceSpecificoA> {}
template <typename Base, typename Elemento>
class Livello2 : public Base {}
class Livello2SpecificoA :
	public Livello2<CompositoSpecificoA,Livello1SpecificoA> {}
E' chiaro che questa struttura e' estremamente accoppiata. Ha senso
perche' tutti gli elementi fondamentali erano concettualmente noti al
momento di iniziare il disegno e perche' si tratta degli elementi
fondamentali del sistema: modificarne la struttura concettuale
rappresenta comunque una tale rivoluzione da giustificare l'elevato
"costo di stravolgimento".
Ciao,
Nicola

Avresti ancora piu` istanze di Base, perche` in uno dei rami
non e` dichiarata come classe base virtuale.
Entrambe le classi devono dichiarare Base come classe base virtuale.
Paolo
--
Paolo Marotta
Duck Communication Entertainment s.r.l.
Largo Arenula, 11 - Roma
http://www.duckce.com
p.ma...@duckce.com
-- 
Posted from 
HA-server-a.cs.interbusiness.it
 [151.99.250.31] 
via Mailgate.ORG Server - 
http://www.Mailgate.ORG

 >Allora esistono per ogni istanza di 'estensione4' due sotto-oggetti
 >di tipo Base: uno ad uso esclusivo di estensione 3 e l'altro in
 >comune fra estensione1 e estensione2.
Corretto...


 >Ma se entrambi possiedo la stessa istanza di base non dovrebbe essere
 >stampato il valore 20 in entrambi i casi, essendo stata l'ultima modifica
 >fatta dal costruttore di estensione2?
Attenzione, i due oggetti non possiedono la stessa istanza di classe
base (sono due rami distinti della gerarchia).
Se hai una classe (nota l'ereditarieta` multipla)
class estensione3 : public estensione2, public estensione 1 { ...};
allora ogni istanza di estensione3 "possiede" una unica istanza
di Base ed in questi casi (ereditarieta` multipla) il costruttore della
classe derivata (estensione3) ha l'obblico di invocare il costruttore
della classe base virtuale. Il compilatore eseguira` solo la chiamata
fatta nel costruttore della classe massimamente derivata.
Per una spiegazione piu` chiara (non ho tempo) rimando ad un
qualsiasi libro (valido) sul C++ o al mio tutorial.


 >Questo è vero, Enrico, ma molte volte ho visto che alcuni usavano
 >specificare con il risolutore di scope se volevano la versione ereditata
 >dalla derived1 o dalla derived2 ed il problema si risolve elegantemente.
 >Quindi penso che questa sia una valida alternativa all'ìereditarietà virtuale
 >che in molti casi può essere limitante.
In generale non e` una valida alternativa, ricorda che con l'ereditarieta`
virtuale c'e` condivisione della classe base (e quindi una interazione
tra le classi derivate utilizzate per ereditare... Cio` non esiste
con la normale ereditarieta`.
Per ulteriori spiegazioni rimando al mio tutorial.
Ad ogni modo l'ereditarieta` virtuale non elimina sempre la necessita`
di utilizzare il risolutore di scope (basta che le classi basi dichiarino
un metodo con identica signature per essere costretti ad utilizzare ::)


 >0                   <--  output oggetto3: costruito tramite Base():
 >perche'?????
In caso di ereditarieta` multipla, e` il costruttore della classe 
(massimamente)
derivata (e solo lui) che ha il compito di inizializzare esplicitamente la 
classe base virtuale
(tramite una chiamata ad un suo costruttore), le chiamate fatte in classi 
derivate intermedie
vengono ignorate.
Nel tuo caso SubDerived non ha invocato esplicitamente alcun costruttore di 
Base
ed il compilatore ha aggiunto (correttamente) una chiamata al default ctor.
