Virtuální dědičnost - Virtual inheritance

Virtuální dědičnost je C ++ technika, která zajišťuje pouze jednu kopii a základní třída'členské proměnné jsou zdědil třídami odvozenými od vnoučat. Bez virtuální dědičnosti, pokud dvě třídy B
a C
dědí z třídy A
a třída D
dědí z obou B
a C
, pak D
bude obsahovat dvě kopie A
'členské proměnné: jedna přes B
a jeden přes C
. Ty budou přístupné samostatně pomocí rozlišení oboru.
Místo toho, pokud třídy B
a C
dědí prakticky ze třídy A
, pak předměty třídy D
bude obsahovat pouze jednu sadu členských proměnných ze třídy A
.
Tato funkce je nejužitečnější pro vícenásobné dědictví, protože dělá virtuální základnu běžnou podobjekt pro odvozující třídu a všechny třídy, které jsou z ní odvozeny. To lze použít, aby se zabránilo diamantový problém objasněním nejednoznačnosti, kterou třídu předků použít, z pohledu odvozené třídy (D
v příkladu výše) virtuální základna (A
) působí, jako by šlo o přímou základní třídu D
, nikoli třída odvozená nepřímo přes základnu (B
nebo C
).[1][2]
Používá se, když dědičnost představuje spíše omezení množiny než složení částí. V C ++ je základní třída zamýšlená jako společná v celé hierarchii označena jako virtuální s virtuální
klíčové slovo.
Zvažte následující hierarchii tříd.
struktur Zvíře { virtuální ~Zvíře() = výchozí; virtuální prázdnota Jíst() {}};struktur Savec: Zvíře { virtuální prázdnota Dýchat() {}};struktur Okřídlené zvíře: Zvíře { virtuální prázdnota Klapka() {}};// Netopýr je okřídlený savecstruktur Netopýr: Savec, Okřídlené zvíře {};Netopýr netopýr;
Jak je deklarováno výše, volání na bat
je nejednoznačný, protože existují dva Zvíře
(nepřímé) základní třídy v Netopýr
, takže jakýkoli Netopýr
objekt má dva různé Zvíře
podobjekty základní třídy. Takže pokus přímo svázat odkaz na Zvíře
podobjekt a Netopýr
objekt by selhal, protože vazba je ze své podstaty nejednoznačná:
Netopýr b;Zvíře& A = b; // chyba: do kterého zvířecího podobjektu by měl Bat vrhnout, // savec :: zvíře nebo okřídlený zvíře :: zvíře?
Abychom se jednoznačně vyjádřili, museli bychom výslovně konvertovat netopýr
na jeden dílčí objekt základní třídy:
Netopýr b;Zvíře& savec = static_cast<Savec&>(b); Zvíře& okřídlený = static_cast<Okřídlené zvíře&>(b);
Aby bylo možné zavolat Jíst
je nutná stejná disambiguace nebo výslovná kvalifikace: static_cast
nebo static_cast
nebo alternativně bat.Mammal :: Eat ()
a bat.WingedAnimal :: Eat ()
. Explicitní kvalifikace používá nejen jednodušší a jednotnou syntaxi pro ukazatele i objekty, ale také umožňuje statické odesílání, takže by to byla pravděpodobně preferovaná metoda.
V tomto případě dvojí dědictví Zvíře
je pravděpodobně nežádoucí, protože chceme modelovat, že vztah (Netopýr
je Zvíře
) existuje pouze jednou; že a Netopýr
je Savec
a je Okřídlené zvíře
neznamená, že se jedná o Zvíře
dvakrát: an Zvíře
základní třída odpovídá smlouvě, která Netopýr
implementuje (vztah „je“ výše znamená „implementuje požadavky") a a Netopýr
implementuje pouze Zvíře
smlouva jednou. Skutečný význam světaje jen jednou "to je Netopýr
by měl mít pouze jeden způsob implementace Jíst
, ne dvěma různými způsoby, podle toho, zda Savec
pohled na Netopýr
je jíst, nebo Okřídlené zvíře
pohled na Netopýr
. (V prvním příkladu kódu to vidíme Jíst
není přepsán ani v jednom Savec
nebo Okřídlené zvíře
, takže dva Zvíře
subobjects will actually behave the same, but this is just a degenerate case, and that does not make a difference from the C ++ pointal view).
Tato situace se někdy označuje jako diamantové dědictví (vidět Diamond problém ), protože schéma dědičnosti má tvar diamantu. Virtuální dědičnost může pomoci tento problém vyřešit.
Řešení
Naše třídy můžeme znovu deklarovat následovně:
struktur Zvíře { virtuální ~Zvíře() = výchozí; virtuální prázdnota Jíst() {}};// Dvě třídy prakticky dědí zvíře:struktur Savec: virtuální Zvíře { virtuální prázdnota Dýchat() {}};struktur Okřídlené zvíře: virtuální Zvíře { virtuální prázdnota Klapka() {}};// Netopýr je stále okřídlený savecstruktur Netopýr: Savec, Okřídlené zvíře {};
The Zvíře
část Netopýr :: WingedAnimal
je nyní stejný Zvíře
instance jako ta, kterou používá Bat :: Savec
, což znamená, že a Netopýr
má pouze jeden, sdílený, Zvíře
instance v jeho reprezentaci a tak volání na Bat :: Jíst
je jednoznačné. Navíc, přímé obsazení od Netopýr
na Zvíře
je také jednoznačné, protože nyní existuje pouze jeden Zvíře
instance, která Netopýr
lze převést na.
Možnost sdílet jednu instanci souboru Zvíře
rodič mezi Savec
a Okřídlené zvíře
je povoleno zaznamenáním posunu paměti mezi Savec
nebo Okřídlené zvíře
členů a členů základny Zvíře
v rámci odvozené třídy. Toto posunutí však může být obecně známé pouze za běhu, tedy Netopýr
musí se stát (vpointer
, Savec
, vpointer
, Okřídlené zvíře
, Netopýr
, Zvíře
). Existují dva vtable ukazatele, jeden na hierarchii dědičnosti, která prakticky dědí Zvíře
. V tomto příkladu jeden pro Savec
a jeden pro Okřídlené zvíře
. Velikost objektu se proto zvýšila o dva ukazatele, ale nyní existuje pouze jeden Zvíře
a žádná dvojznačnost. Všechny objekty typu Netopýr
použije stejné vpointery, ale každý Netopýr
objekt bude obsahovat svůj vlastní jedinečný Zvíře
objekt. Pokud z něj dědí jiná třída Savec
, jako Veverka
, pak vpointer v Savec
část Veverka
se bude obecně lišit od vpointeru v Savec
část Netopýr
i když by se mohlo stát, že budou stejné, pokud Veverka
třída má stejnou velikost jako Netopýr
.
Reference
- ^ Milea, Andrei. „Řešení diamantového problému s virtuální dědičností“. Cprogramming.com. Citováno 2010-03-08.
Jedním z problémů, které vznikají v důsledku vícenásobného dědictví, je diamantový problém. Klasickou ilustraci uvádí Bjarne Stroustrup (tvůrce C ++) v následujícím příkladu:
- ^ McArdell, Ralph (14.02.2004). „C ++ / Co je to virtuální dědičnost?“. Všichni odborníci. Archivovány od originál dne 10.01.2010. Citováno 2010-03-08.
To je něco, co podle vás může být vyžadováno, pokud používáte vícenásobné dědictví. V takovém případě je možné, aby byla třída odvozena z jiných tříd, které mají stejnou základní třídu. V takových případech, bez virtuální dědičnosti, budou vaše objekty obsahovat více než jeden podobjekt základního typu, který základní třídy sdílejí. To, zda je to požadovaný účinek, závisí na okolnostech. Pokud tomu tak není, můžete použít virtuální dědičnost zadáním virtuálních základních tříd pro ty základní typy, pro které by měl celý objekt obsahovat pouze jeden takový podobjekt základní třídy.