Ugrás a tartalomhoz

Virtuális öröklődés

A Wikipédiából, a szabad enciklopédiából
A lap korábbi változatát látod, amilyen Whitepixels (vitalap | szerkesztései) 2021. március 25., 08:10-kor történt szerkesztése után volt. Ez a változat jelentősen eltérhet az aktuális változattól. (0)
A gyémántöröklődés képe, egy olyan probléma, amelyet a virtuális öröklődés megpróbál megoldani

A virtuális öröklődés egy C++ technika, amely biztosítja, hogy az ősosztály tagváltozóinak csak egy példányát örököljék a származtatott osztályok. Virtuális öröklődés nélkül, ha két B és C osztály örököl egy A osztályból, és D osztály örököl mind a B, mind a C osztályból, akkor a D osztály tartalmaz két példányt az A tagváltozóból, egyet a B osztályon keresztül, egyet pedig a C osztályon keresztül. Ezek függetlenül, hatókör-felbontással lesznek elérhetők.

Ehelyett ha a B és a C osztály az A osztályból virtuálisan örököl, akkor a D osztály objektumai csak az A osztály tagváltozóinak halmazát tartalmazzák.

Ez a szolgáltatás a leginkább a többszörös öröklődésnél hasznos, mivel a virtuális őst a mellékobjektum közös elemévé teszi a származtatandó osztály és az abból származó összes osztály számára. Ez felhasználható a gyémántprobléma elkerülésére, azáltal, hogy egyértelművé teszi, hogy melyik ősosztályt kell használni, mivel a származtatandó osztály szempontjából (a fenti példában a D osztály) a virtuális alap (A) úgy viselkedik, mintha a közvetlen ősosztály lenne. D nem egy ősosztályon keresztül származtatott osztály (B vagy C).[1][2]

Ezt akkor használják, amikor az öröklődés egy készlet korlátozását jelenti, nem pedig a komponensek összetételét. A C++ rendszerben egy olyan alaposztályt, amely a hierarchia egészében egységes, virtuálisnak kell jelölni a virtual kulcsszóval.

Adott a következő osztályhierarchia.

struct Állat {
virtual ~Állat() = default;
virtual void Eszik() {}
};
struct Emlős : Állat {
 	virtual void Lélegzik() {}
};
struct SzárnyasÁllat : Állat {
 	virtual void SzárnyCsapás() {}
};
//A denevér egy szárnyas emlős.
struct Denevér: Emlős, SzárnyasÁllat {};

Denevér denevér;

Mint ahogy fent is deklaráltuk, egy Denevér.Eszik meghívás félreérthető, mert két Állat (közvetlen) ősosztály a denevérben, szóval bármelyik denevér objektumnak van két különböző Állat ősosztály mellékobjektuma. Szóval megpróbálni közvetlenül hozzá kötni egy Denevér objektumnak az Állat mellékobjektumát nem járna sikerrel, mivel a kötés öröklése nem egyértelmű.

Denevér d;
Állat& a = d;  // error

Az egyértelműség érdekében az egyiknek át kell alakulnia valamelyik ősosztályba.

Denevér d;
Állat& emlős = static_cast<Emlős&>(d);
Állat& szárnyas = static_cast<SzárnyasÁllat&>(d);

Az Eszik hívásához ugyanazon az egyértelműsítésre vagy explicit minősítésre van szükség: static_cast<Emlős&>(denevér).Eszik() vagy static_cast<SzárnyasÁllat&>(denevér).Eszik() vagy alternatív módon denevér.Emlős :: Eszik() és denevér.SzárnyasÁllat :: Eszik(). Az explicit kvalifikáció nem csupán könnyebb, egységesebb szintaxist alkalmaz mind a mutatók az objektumok számára, de lehetővé teszi a statikus továbbítást is, tehát vitathatatlanul ez lenne a preferált módszer.

Ebben az esetben az Állat kettős öröklődése valószínűleg nemkívánatos, mivel azt akarjuk modellezni, hogy a kapcsolat (a denevér egy állat) csak egyszer létezik, az, hogy a denevér Emlős és SzárnyasÁllat, nem jelenti azt, hogy kétszer is Állat, az Állat alaposztály egy olyan szerződésnek felel meg, amelyet a Denevér indít (a „is-a” kapcsolat valóban azt jelenti, hogy „végrehajtja a követelményeket”) és egy denevér csak egyszer Állat. A „csak egyszer” való világbéli jelentése az, hogy a Denevérnek csak egyik módja lehet az Eszik() megvalósításához, nem pedig két különféle módja, attól függően hogy a denevért Emlős része eszik-e, vagy a SzárnyasÁllat része. (Az első kódrészletben azt látjuk, hogy az Eszik nem íródik felül, sem az Emlős-ben, sem a SzárnyasÁllat-ban, tehát a két Állat mellékobjektuma ugyanúgy fog viselkedni, de ez csak egy degenerált eset de nem különbözik a C++ szemszögéből.

A megoldás

Az osztályokat a következők szerint deklarálhatjuk újra:

struct Állat {
 	virtual ~Állat() = default;
 	virtual void Eszik() {}
};
//Két osztály virtuálisan örökli az Állat-ot:
struct Emlős: virtual Állat {
 	virtual void Lélegzik() {}
};
struct SzárnyasÁllat: virtual Állat {
 	virtual void SzárnyCsapás() {}
};
//A denevér még mindig egy szárnyas állat
struct Denevér: Emlős, SzárnyasÁllat {};

A Denevér:SzárnyasÁllat Állati része most ugyanazon példányra mutat, mint a Denevér:Emlős által használt Állat, azaz egy denevérnek csak egy, megosztott, állati példánya van a reprezentációjában, tehát egy Denevér hívásánál az Eszik egyértelmű. Ezenkívül a Denevérről az Állatra történő kasztolás szintén egyértelmű, mivel már csak egy Állat példány létezik, amelybe a Denevér át konvertálható.

Annak a képessége, hogy megosztunk egyetlen egy példányt az Állat ősből az Emlős és a SzárnyasÁllatok leszármazottja között. Két virtuális táblát tartalmazó mutató van jelen, egy az öröklés hierarchiáját tartja számon. Ebben a példában egy az Emlősre és egy a SzárnyasÁllat-ra az Állat-ból származtatva. Az objektum mérete így növekedett 2 mutatóval ebben az esetben, azonban most már csak egyetlen egy Állat objektumot kell kezelni.

Jegyzetek

  1. Milea, Andrei: Solving the Diamond Problem with Virtual Inheritance. Cprogramming.com . (Hozzáférés: 2010. március 8.) „One of the problems that arises due to multiple inheritance is the diamond problem. A classical illustration of this is given by Bjarne Stroustrup (the creator of C++) in the following example:”
  2. McArdell, Ralph: C++/What is virtual inheritance?. All Experts , 2004. február 14. [2010. január 10-i dátummal az eredetiből archiválva]. (Hozzáférés: 2010. március 8.) „This is something you find may be required if you are using multiple inheritance. In that case it is possible for a class to be derived from other classes which have the same base class. In such cases, without virtual inheritance, your objects will contain more than one subobject of the base type the base classes share. Whether this is what is the required effect depends on the circumstances. If it is not then you can use virtual inheritance by specifying virtual base classes for those base types for which a whole object should only contain one such base class subobject.”