La presentazione è in caricamento. Aspetta per favore

La presentazione è in caricamento. Aspetta per favore

Ereditarietà multipla C++ method vs Twin-Objects

Presentazioni simili


Presentazione sul tema: "Ereditarietà multipla C++ method vs Twin-Objects"— Transcript della presentazione:

1 Ereditarietà multipla C++ method vs Twin-Objects
Daniela Briola Orlin Velinov

2 Ereditarietà singola A B Nell’esempio la classe B è derivata dalla classe A. Ogni linguaggio Object Oriented supporta il concetto di ereditarietà Si dice che una classe è derivata da un’altra quando ne estende le funzionalità grazie all’inheritance

3 Ereditarietà multipla
Permette di comporre classi derivando da più classi base. A B C

4 Ereditarietà multipla
Vantaggi Svantaggi Complicazione notevole del linguaggio che la implementa Possibilità di comporre velocemente oggetti molto complessi Aggregare funzionalità differenti in un’unica classe Scarsa efficienza anche quando non viene usata Soluzione elegante e di grande utilità Rischio elevato di “name clash”

5 Ereditarietà multipla: il problema del diamante
Se una classe eredita membri con lo stesso nome da più di un genitore, avviene un conflitto A B1 B2 C ? Ci sono due strategie possibili: gestire direttamente il grafo di ereditarietà trasformarlo in una catena lineare (ereditarietà singola)

6 Ereditarietà multipla: il problema del diamante
La semantica dei linguaggi orientati al grafo modella direttamente l’albero di derivazione Se un membro è definito solo dalla classe A, è ereditato da B1 e da C senza errori Si deve prevedere il caso di ridefinizione dei metodi doppi da parte di B2 A B1 B2 C

7 Ereditarietà multipla: il problema del diamante
Soluzione Lineare: La gerarchia di derivazione viene linearizzata Si elimina l’invocazione multipla di metodi della soluzione precedente Svantaggi Lo sviluppatore non è al corrente della gerarchia di sottotipazione implicita La selezione del metodo da utilizzare è a discrezione del compilatore Problemi nel collegamento con il genitore effettivo A B2 B1 C

8 Ereditarietà multipla
Implementazione C++

9 C++ Ereditarietà singola
In C++ una oggetto è una regione di memoria contigua class A { int a; void f(int i); }; int a A* pa; pa->f(2); Nell’area di memoria riservata all’oggetto pa viene solo salvato un intero La funzione f, essendo non-virtual (statica), è definita esternamente all’oggetto pa, quindi è come fosse una funzione/procedura normale.

10 C++ Ereditarietà singola
Gli oggetti composti (derivati) sono costruiti dal compilatore concatenando le aree di memoria. int a class A { int a; void f(int); }; class B : A { int b; void g(int); }; class C : B { int c; void h(int); }; int b int c Se la classe definisce metodi virtual entra in gioco la tabella delle funzioni (VMT): ogni oggetto ha un puntatore alla VMT, che permette di identificare la funzione effettivamente da usare.

11 C++ Polimorfismo Il polimorfismo è un concetto fondamentale della programmazione OO Consente che gli oggetti assumano comportamenti differenti a seconda del contesto in cui operano In particolare se Ptr è un puntatore di tipo T, allora Ptr può puntare non solo a istanze di tipo T ma anche a istanze di classi derivate da T T* Ptr = 0; // Puntatore nullo /* ... */ Ptr = new Td; // Td è una classe derivata da T

12 C++ Polimorfismo (2) C++ fa in modo che il corretto tipo dell’oggetto venga determinato automaticamente alla chiamata della funzione In questo modo il linking della funzione viene rimandato a runtime (binding dinamico) Per fare ciò bisogna dichiarare la funzione membro virtual class T { public: virtual void Paint(); };

13 C++ Polimorfismo: Implementazione
I metodi virtuali vengono ereditati allo stesso modo di quelli non virtual, possono anch'essi essere sottoposti a overloading ed essere ridefiniti non c'e` alcuna differenza eccetto che una loro invocazione viene risolta a run-time In una classe con un metodo virtuale, il compilatore associa alla classe (non all'istanza) una tabella (VMT) che contiene per ogni metodo virtuale l'indirizzo alla corrispondente funzione Ogni istanza di quella classe conterrà poi al suo interno un puntatore (VPTR) alla VMT

14 C++ Polimorfismo: Overhead
L'invocazione di un metodo virtuale e` piu` costosa di quella per una funzione membro ordinaria, tuttavia il compilatore puo` evitare tale overhead risolvendo a compile-time tutte quelle situazioni in cui il tipo e` effettivamente noto Td Obj1; T* Ptr = 0; Obj1.Paint(); // Chiamata risolvibile staticamente Ptr->Paint(); // Questa invece no

15 Polimorfismo nell’ereditarietà multipla
class A { virtual void f(); }; class B { virtual void f(); virtual void g() }; class C: A, B { void f(); }; A* pa = new C; B* pb = new C; C* pc = new C; pa->f(); pb->f(); pc->f(); C eredita sia da A che da B, dunque l’assegnazione è corretta Tutte tre le chiamate invocano C::f()

16 C++ Classi astratte Funzioni virtuali pure
Ereditarietà e polimorfismo possono essere combinati per realizzare classi il cui unico scopo è creare una interfaccia comune a una gerarchia di classi class TShape { virtual void Paint() = 0; virtual void Erase() = 0; }; Funzioni virtuali pure Una classe che possiede funzioni virtuali pure è detta classe astratta e non è possibile istanziarla Può essere utilizzata unicamente per derivare nuove classi forzandole a fornire determinati metodi

17 C++ Ereditarietà multipla: Ambiguità
Analizziamo il caso in cui la classe D derivi da B1 e B2 class Base1 { public: void f(); }; class Base2 { void f2(); class Derived : Base1, Base2 { // Non ridefinisce f() B1 B2 D La classe Derived eredita piu` volte gli stessi membri, in particolare la funzione f()

18 C++ Ereditarietà multipla: Ambiguità – soluzione esplicita
Derived x; x.f() //Errore, è ambiguo! N.B.: questo è un errore che appare solo a runtime Soluzione: Derived x; x.B1::f() quanto detto vale anche per gli attributi; non è necessario che la stessa definizione si trovi in più classi basi dirette, è sufficiente che essa giunga alla classe derivata attraverso due classi basi distinte il problema non si sarebbe posto se Derived avesse ridefinito la funzione membro f().

19 C++ Ereditarietà multipla: Ambiguità - Implementazione
Come implementa il C++ una soluzione esplicita attraverso qualificatore x.b1::f() ? b1::f() si aspetta un puntatore b1* (che diventa il suo this) A runtime conosciamo però solo il puntatore della classe derivata “derived” Il compilatore aggiunge un opportuno delta (memorizzato nella VMT) per raggiungere la parte relativa a B1 in “derived” In pratica, il compilatore trasforma una chiamata diretta in una indiretta, sommando un offset

20 C++ Ereditarietà multipla: Implementazione
VMT Parte B1 Delta(B2) C::f() Parte B2 C::f() -delta(B2) B2::g() Parte D

21 C++ Ereditarietà multipla: Ambiguità
Il problema dell'ambiguità può essere portato al caso estremo in cui una classe erediti più volte una stessa classe base class Base { }; class Derived1 : Base { }; class Derived2 : Base { }; class Derived3 : Derived1, Derived2 { };

22 C++ Ereditarietà multipla: Ambiguità
Derived3 Derived1 Derived2 ? Base Base

23 C++ Ereditarietà multipla: Ambiguità – ereditarietà virtuale
Il C++ permette di risolvere il problema molto elegantemente con l’uso di classi base virtuali class Base { }; class Derived1 : virtual Base { }; class Derived2 : virtual Base { }; class Derived3 : Derived1, Derived2 { }; Quando una classe eredita tramite la keyword virtual il compilatore non copia il contenuto della classe base nella classe derivata, ma inserisce nella classe derivata un puntatore ad un’unica istanza della classe base

24 C++ Ereditarietà multipla: Ambiguità – ereditarietà virtuale
Derived3 Derived1 virtual Base Derived2

25 C++ Ereditarietà multipla: Ambiguità – ridefinizione
In alcuni casi l’ambiguità persiste. Supponiamo che una delle classi intermedie ridefinisca una funzione membro della classe base. class Base { public: void DoSomething(); }; class Derived1 : virtual Base { class Derived2 : virtual Base { class Derived3 : Derived1, Derived2 { }; Se Derived3 non ridefinisce DoSomething si crea ambiguità! Quale metodo usare? Il compilatore C++ segnala errore!

26 C++ Ereditarietà multipla: Ambiguità – ridefinizione
La situazione è diversa se solo una delle classi intermedie fa la ridefinizione class Base { public: void DoSomething(); }; class Derived1 : virtual Base { class Derived2 : virtual Base { /* … */ }; class Derived3 : Derived1, Derived2 { }; Solo Derived1 ridefinisce DoSomething (definizione dominante) Il compilatore C++ non segnala errore!

27 C++ Ereditarietà multipla: Ambiguità – Esempio
La “virtualità” di una classe non è una sua caratteristica, ma è data dall’essere dichiarata come tale nelle classi che la ereditano. Vediamo un esempio: In questo caso la classe C avrà un solo riferimento ad un oggetto di classe L class A : virtual L {...}; class B : virtual L {...}; class C : A , B {...}; In questo caso invece la classe D avrà due “sotto-oggetti” di tipo L, un virtuale e uno normale class D : L,C{...};

28 C++ Ereditarietà multipla: i costruttori
Le classi derivate normalmente chiamano implicitamente (o esplicitamente) i costruttori delle classi base In caso di ereditarietà multipla con classe base virtuale si pone il problema di decidere chi inizializza la classe base (in quale ordine) In C++ le classi virtual sono inizializzate dalle classi massimamente derivate In generale i costruttori sono eseguiti nell’ordine in cui compaiono nella dichiarazione eccetto quelli delle classi virtual, eseguiti prima

29 C++ Ereditarietà multipla: i distruttori
Stesso discorso per i distruttori, ma in ordine contrario Il compilatore C++ si preoccupa di distruggere le classi virtual una sola volta, anche se vengono ereditate molteplici volte

30 C++ Ereditarietà multipla: Problemi di efficienza
L’ereditarietà multipla comporta alcuni costi in termini di efficienza: Sottrazione di una costante per ogni accesso ai membri delle classi base Un word per funzione in ogni VMT (per il delta) Un riferimento in memoria ed una sottrazione per ogni chiamata a funzione virtuale Un riferimento in memoria ed una sottrazione per ogni accesso ai membri di una classe base virtuale La 1 e la 4 sono penalizzanti solo se l’ereditarietà multipla è effettivamente usata. La 2 e la 3 sempre

31 C++ Ereditarietà multipla: Considerazioni
Il metodo qui presentato offre due modalità di estendere il “name space” di una classe: classe base classe base virtuale Comunque, le regole per gestire questi due tipi di classi sono indipendenti dal tipo di classe effettivamente usata, inoltre: le ambiguità sono illegali le regole per la gestione dei vari membri sono le stesse che con ereditarietà singola le regole di visibilità ed inizializzazione sono le stesse dell’ereditarietà singola Violazioni di queste regole sono segnalate a compile-time

32 C++ Ereditarietà multipla: Conclusioni
L’ereditarietà multipla, in una forma pratica da usare, è relativamente semplice da aggiungere al C++ Per essere implementata richiede piccolissime modifiche alla sintassi e si adatta naturalmente alla già preesistente struttura L’implementazione è efficiente sia in tempo che in spazio, dal momento che, soprattutto su calcolatori moderni, semplici operazioni di somma o sottrazione o un campo in più nella VMT non costituiscono un overhead pesante La compatibilità con il C non è compromessa, e neppure la portabilità

33 Ereditarietà multipla
Implementazione modello “Twin Objects” (J.Templ)

34 Twin Objects Sono un modo di realizzare l’ereditarietà multipla usando l’ereditarietà singola Possono essere usati per implementare l’ereditarietà multipla in linguaggi che non la supportano, ad esempio in Java Aiutano a risolvere problemi tipici dell’ereditarietà multipla quali l’ambiguità dei nomi

35 Twin Objects - modello B B CB CA C A C A Multiple Inheritance
CA e CB sono chiamati twins (gemelli) Sono sempre generati assieme e legati dai puntatori T1 e T2

36 Twin Objects - modello B E C A CA CE CB
Se la classe C eredita da n classi base, ci saranno n twins da gestire A B E CA CE C CB

37 Twin Objects - modello P2 C2 C P1
Se dobbiamo inserire nella nostra classe attributi o metodi aggiuntivi (non definiti nelle classi basi) abbiamo due metodi: creiamo una classe aggiuntiva C in cui li inseriamo P1 P2 C2 C T1 T2 li mettiamo in uno dei due twin (ad es. in C1, che chiamiamo C); questo è l’approccio seguito normalmente

38 Twin Objects - ereditarietà
P1 P2 C2 C T1 T2 D2 D1 C2 P1 P2 C T1 T2 D No D non eredita da C2!

39 Twin Objects - Collaborazione
Ogni classe figlio è responsabile per la comunicazione con il suo padre e si occupa di inoltrare i messaggi alle classi gemelle I client referenziano uno dei figli direttamente, e tutti gli altri tramite i puntatori a twin (la ‘T’ negli esempi) I client che necessitano di comunicare con uno dei Padri, lo fanno attraverso la rispettiva classe-figlio

40 Twin Objects - Implemenazione
Astrazione: le classi gemelle devono cooperare strettamente tra loro, permettendo di accedere ai loro membri privati (visibilità package in Java). Il tutto deve apparire come un unico oggetto dall’esterno. Efficienza: l’uso di twin objects sostituisce le relazioni per ereditarietà con relazioni per composizione. Ciò comporta la necessità di inoltrare messaggi e quindi minore efficienza, ma poiché l’ereditarietà multipla è in genere più lenta, non si notano differenze sostanziali.

41 Twin Objects - Ottimizzazioni
Raggruppare i Twin in un unico blocco contiguo per velocizzare l’allocazione dell’oggetto che li usanecessità di utilizzare puntatori nel blocco per collegare i twin sostituire il puntatore con un offset relativo all’inizio del twin se l’allocazione degli oggetti client e dei twin è resa uguale per ogni istanza, l’offset è una costante memorizzabile a parte le VMT possono essere memorizzate contiguamente in un blocco

42 Twin Objects - Esempio Java non consente l’ereditarietà multipla, tuttavia in alcuni casi serve poter mettere assieme oggetti di natura diversa, ad esempio implementando applet che reagiscono alle azioni del mouse Costruendo un applet, serve poter ereditare da un generico Applet a cui verrà ridefinito il metodo .Paint() e da una classe StdMouseListener di cui verranno ridefiniti i metodi mousePressed(), mouseClicked() e mouseReleased() Lo schema che segue riassume la struttura del nostro oggetto composto MyApplet + MyAppletListener

43 Twin Objects – Esempio C Applet resize() paint() … StdMouseListener
mousePressed() mouseClicked() mouseReleased() paint() C T1 T2 MyApplet mousePressed() mouseClicked() mouseReleased() MyAppletListener

44 Twin Objects - Esempio class Applet { public void paint();
public void resize(); } class StdMouseListener { public void mousePressed(); public void mouseClicked(); public void mouseReleased(); } Queste sono le definizioni delle due classi base di cui desideriamo fare ereditarietà multipla attraverso l’uso del modello Twin Objects

45 Twin Objects - Esempio class MyApplet extends Applet {
MyAppletListener listener; /* il Twin */ public void paint() { /* ridefinisco */ } } class MyAppletListener extends StdMouseListener { MyApplet applet; /* il Twin */ public void mousePressed () { /* ridefinisco */ } public void mouseClicked () { /* ridefinisco */ } public void mouseReleased () { /* ridefinisco */ } } Ogni “twin” eredita il proprio parent ridefinendone i metodi opportuni e si occupa di comunicare con il proprio fratello.

46 Twin Objects - Esempio Layout in memoria:
MyApplet Applet MyStdMouseListener StdMouseListener twins Come evidente, si tratta di oggetti completamente separati a livello di memoria. Il link tra le classi è fatto a livello di applicazione.

47 C++ - Contro esempio class Applet { virtual void paint();
virtual void resize(); } class StdMouseListener { virtual void mousePressed(); virtual void mouseClicked(); virtual void mouseReleased(); Come per l’esempio Twin Objects, abbiamo due classi base iniziali da cui vogliamo fare ereditarietà multipla... “virtual” indica che le funzioni sono di tipo latebinding (come nell’es. Java) e non statiche.

48 C++ - Contro esempio class MyApplet : Applet, StdMouseListener {
void paint() { /* ridefinisco */ } void mousePressed() { /* ridefinisco */ } void mouseClicked() { /* ridefinisco */ } void mouseReleased() { /* ridefinisco */ } } Il C++ ci consente di avere un’unica classe MyApplett che eredita contemporaneamente da Applet e StdMouseListener. Ridefinisco i metodi secondo le esigenze; per fortuna non ci sono name clashes quindi non ci preoccupiamo di usare qualificatori espliciti A livello implementativo, il compilatore traduce una chiamata del tipo *obj->paint() in una chiamata indiretta sommando un delta riferito alla classe parent e memorizzato nella VMT

49 C++ contro esempio VMT Parte Applet Parte Stdmouselistener
delta(StdML) Myapp::paint() Parte Stdmouselistener Myapp::paint() -delta(StdML) metodi ridefiniti da MyApplet Parte Myapplet

50 Considerazioni di Templ sull’ereditarietà multipla
L’implementazione dell’ereditarietà multipla del C++ porta ad un overhead anche dell’ereditarietà singola Il codice deve essere riaggiustato per cambiare il “self” L’ereditarietà multipla in se stessa non è né molto utile né veramente necessaria dal momento che può essere facilmente simulata Il vantaggio che offre è puramente sintattico Il suo costo non è giustificato dalle opportunità che offre

51 Conclusioni L’ereditarietà multipla è uno strumento potente che consente di affrontare problemi complessi con eleganza La sua implementazione nativa può generare un lieve decadimento del performances anche quando non viene usata (vedi C++) Gestire un linguaggio con ereditarietà multipla può divenire comunque complesso e poco chiaro (sia per il programmatore che per l’implementatore) I linguaggi moderni tendono ad evitarla (es. Java), adoperando tecniche altrettanto efficaci come i Twin Objects, senza overhead e complicazioni, dal momento che i vantaggi, sebbene ci siano, forse non giustificano le necessarie modifiche dei linguaggi OO già esistenti

52 Riferimenti “Twin – A Design Pattern for Modeling Multiple Inheritance” J. Templ “Multiple Inheritance for C++” Bjarne Stroustrup Manuale del C++ Bjarne Stroustrup


Scaricare ppt "Ereditarietà multipla C++ method vs Twin-Objects"

Presentazioni simili


Annunci Google