(Translated by https://www.hiragana.jp/)
虚继承 - 维基百科,自由的百科全书
对于继承概念がいねんちゅうてききょ函数かんすう,请参阅きょ函数かんすう

きょ继承 これめんこう对象编程なかてきいち种技术,ゆび一个指定的基类,ざい继承体系たいけい结构ちゅうはた其成员数すえ实例どもとおる给也从这个基类型直接ちょくせつある间接派生はせいてき其它类。

举例らい说:かり如类AかずB各自かくじ从类X派生はせいきょ继承且假设类X包含ほうがん一些数据成员),且类Cどう继承ABCてき对象就会拥有两套Xてき实例すうすえ可分かぶん独立どくりつ访问,一般要用适当的消歧义限定符)。ただし如果类AあずかB各自かくじきょ继承りょうXCてき对象就只包含ほうがんいち套类Xてき实例すうすえ。对于这一概念典型实现的编程语言是C++

这一特性とくせいざい多重たじゅう继承应用ちゅう非常ひじょう有用ゆうよう以使とくきょもと类对于由它直接ちょくせつある间接派生はせいてき类来说,拥有一个共同的基类对象实例。避免よし于带ゆう歧义てき组合而产せいてき问题(如“菱形ひしがた继承问题”)。其原そのはら,间接派生はせい类(C穿ほじとおるりょう其父类(上面うわつられい子中こなかてきAあずかB),实质じょう直接ちょくせつ继承りょうきょもとX[1][2]

这一概念がいねん一般いっぱんよう于“继承”ざいひょう现为いち整体せいたい,而非几个部分ぶぶんてき组合时。ざいC++ちゅう类可以通过使用しよう关键virtualらい声明せいめいきょ继承关系。

问题てき产生

编辑

こう虑下めんてき类的层次关系。

class Animal {
 public:
  virtual void eat();
};

class Mammal : public Animal {
 public:
  virtual void breathe();
};

class WingedAnimal : public Animal {
 public:
  virtual void flap();
};

// A bat is a winged mammal
class Bat : public Mammal, public WingedAnimal {
};

Bat bat;

按照上面うわつらてきてい义,调用bat.eat()ゆう歧义てきいん为在Bat中有ちゅうう两个Animalもと类(间接てき),所以ゆえん所有しょゆうてきBat对象ゆう两个不同ふどうてきAnimalもと类的对象。よし此,尝试直接ちょくせつ引用いんようBat对象てきAnimal对象かい导致错误,いん为该继承ゆう歧义てき

Bat b;
Animal &a = b; // error: which Animal subobject should a Bat cast into, 
               // a Mammal::Animal or a WingedAnimal::Animal?

ようけしじょ歧义,需要じゅよう显式てきしょうbat转换为每一个基类子对象:

 
菱形ひしがた类继承图示。
Bat b;
Animal &mammal = static_cast<Mammal&> (b); 
Animal &winged = static_cast<WingedAnimal&> (b);


为了せい确的调用eat(),还需要じゅようしょうどうてき以消歧义てき语句:static_cast<Mammal&>(bat).eat()あるstatic_cast<WingedAnimal&>(bat).eat().

ざい这个れい子中こなかわが可能かのう并不需要じゅようAnimal继承两次,わが们只そう建立こんりゅう一个模型来说明这层关系(Bat ぞくAnimal);BatこれMammal也是WingedAnimal并不意味いみ它是两个AnimalAnimalてい义的こうのうゆかりBatらい实现(上面うわつらこれてき属性ぞくせい实际じょう实现需求てき含义),且一个Batただ实现いち。“ただこれいち个”てき真正しんせい含义Batただゆういち种实现eat()てき方法ほうほう,无论Mammalてき角度かくど还是从WingedAnimalてき角度かくどらい。(ざい上面うわつらてきだい一段代码示例中我们看到eat()并没ゆうざいMammalあるWingedAnimalちゅうじゅう载,しょ以这两个Animal对象实际じょう以相どうてき方式ほうしき运作,ただし这只一个不完善的例子,从C++てき角度かくどらい二者之间正好没有实际的区别。)

わかはた上面うわつらてき关系以图がた方式ほうしき表示ひょうじおこりらい类似菱形ひしがたしょ以这一情况也被称为菱形ひしがた继承きょ继承以解决这いち问题。

かい决方ほう

编辑

わが们可以按如下方式ほうしきおもしん声明せいめい上面うわつらてき类:

class Animal {
 public:
  virtual void eat();
};

// Two classes virtually inheriting Animal:
class Mammal : public virtual Animal {
 public:
  virtual void breathe();
};

class WingedAnimal : public virtual Animal {
 public:
  virtual void flap();
};

// A bat is still a winged mammal
class Bat : public Mammal, public WingedAnimal {
};

Bat::WingedAnimalなかてきAnimal部分ぶぶん现在Bat::MammalなかてきAnimal部分ぶぶんしょうどうてきりょう,这也就是说Bat现在ゆう且只ゆういち个共とおるてきAnimal部分ぶぶん所以ゆえん对于Bat::eat()てき调用就不さいゆう歧义りょう。另外,直接ちょくせつはたBat实例分派ぶんぱAnimal实例てき过程也不かい产生歧义りょういん为现ざいただ存在そんざい一種可以转换为AnimalてきBat实体りょう

いんMammal实例てきおこりはじめAnimal部分ぶぶんてきないそんへんうつりりょうちょくいたほどじょ运行分配ぶんぱいないそん时才かいあきら确,所以ゆえんきょ继承应用给MammalWingedAnimal建立こんりゅうりょうきょひょう(vtable)ゆび针(“vpointer”)。よし此“Bat”包含ほうがんvpointer, Mammal, vpointer, WingedAnimal, Bat, Animal。这里共有きょうゆう两个きょひょうゆび针,其中さい派生はせい类的对象しょ指向しこうてききょひょうゆび针,指向しこうりょうさい派生はせい类的きょひょう;另一个虚表指针指向了WingedAnimalてき类的きょひょうAnimalきょ继承而来。ざい上面うわつらてきれいさといち分配ぶんぱいMammal,另一个分配ぶんぱいWingedAnimalよし此每个对ぞううらないようてきないそん增加ぞうかりょう两个ゆび针的大小だいしょうただし却解决了Animalてき歧义问题。所有しょゆうBat类的对象包含ほうがん这两个虚ゆび针,ただしごと一个对象都包含唯一的Animal对象。かり设一个类Squirrel声明せいめい继承りょうMammalSquirrelなかてきMammal对象てききょゆび针和BatなかてきMammal对象てききょゆび针是不同ふどうてきつきかん们占ようてきないそんそら间大しょうしょうどうてき。这是いん为在ないそんちゅうMammalいたAnimalてき距离しょうどうてききょひょう不同ふどう而实际上うらないようてきそら间相どう

きょもと类的はつはじめ

编辑

よし于虚もと类是派生はせい类共とおるてきもと类,いん此由谁来はつはじめきょもと类必须明确。C++标准规定,ゆかりさい派生はせい直接ちょくせつはつはじめきょもと类。よし此,对间せっ继承りょうきょもと类的类,也必须能直接ちょくせつ访问其虚继承らいてき祖先そせん类,也即应知どう其虚继承らいてき祖先そせん类的へんうつり值。

れい如,つね见的“菱形ひしがたきょ继承れい子中こなか,两个派生はせい类、いち个最派生はせい类的构造函数かんすうてきはつはじめれつひょうちゅう以给きょもと类的はつはじめただしただゆかりさい派生はせい类的构造函数かんすう实际执行きょもと类的はつはじめ

g++あずかむなし继承

编辑

g++编译生成せいせいてきC++类实れいきょ函数かんすうあずかきょもと类地へんうつり共用きょうよういち个虚ひょう(vtable)。类实れいてき开始处即为指向しこう所属しょぞく类的きょゆび(vptr)。实际じょう,一个类与它的若干祖先类(ちち类、祖父そふ类、...)组成部分ぶぶん共用きょうよういち个虚ひょうただし各自かくじ使用しようてききょひょう部分ぶぶんつぎしょうせっあいじゅう叠。

g++编译いち个类实例てききょゆび指向しこう该类きょひょうちゅうてきだい一个虚函数的地址。如果该类ぼつゆうきょ函数かんすうあるものきょ函数かんすううつしにゅうりょう祖先そせん类的きょひょうくつがえ盖了祖先そせん类的对应きょ函数かんすう),いん而该类自身じしんきょひょうちゅうぼつゆうきょ函数かんすう需要じゅようはまいれただし该类ゆうきょ继承てき祖先そせん类,则仍しか必须よう访问きょひょうちゅうてききょもと类地へんうつり值。这种じょう况下,该类仍然需要じゅようゆうきょひょう,该类实例てききょゆび指向しこう类虚ひょうちゅういち个值为0てき条目じょうもく

该类其它てききょ函数かんすうてきはまざいきょひょうちゅうだい一个虚函数条目之后(うちそんてい向高むこうたか方向ほうこう)。きょひょうちゅうだい一个虚函数条目之前(うちそん高向たこうてい方向ほうこう),はまにゅうりょうtypeinfo(よう于RTTI)、きょゆび针到せい个对ぞう开始处的へんうつり值、きょもと类地へんうつり值。よし此,如果一个类虚继承了两个类,么对于32ほどじょきょ继承てきひだりちち类地へんうつり值位于vptr-0x0c,きょ继承てきみぎちち类地へんうつり值位于vptr-0x10.

一个类的祖先类有复杂的虚继承关系,则该类的かく个虚もと类偏うつり值在きょひょうちゅうてきそん储顺じょ尊重そんちょう该类到祖先そせんてき深度しんど优先へん历次じょ

Microsoft Visual C++あずかむなし继承

编辑

Microsoft Visual C++あずかg++不同ふどう类的きょ函数かんすうあずかむなしもと类地へんうつり值分别放にゅうりょう两个きょひょうちゅう前者ぜんしゃしょう为虚函数かんすうひょうvftbl,きさきしゃしょうきょもと类表vbtbl。よし此一个类实例可能かのうゆう两个きょゆび针分别指向しこう类的きょ函数かんすうひょうあずかむなしもと类表,这两个虚ゆび针分别称为虚函数かんすうひょうゆび针vftblあずかむなしもと类表ゆび针vbtbl。当然とうぜん,类实れい也可以只ゆういち个虚ゆび针,あるものぼつゆうきょゆび针。きょゆび针总ざい类实れいてきすうすえなり员之まえ,且虚函数かんすうひょうゆび针总ざいきょもと类表ゆび针之まえよし而,对于ぼう个类实例らい说,如果它有きょもと类指针,么虚もと类指针可能かのうざい类实れいてき0节偏うつり处,也可能かのうざい类实れいてき4节偏うつり处(对于32ほどじょらい说),这给类成员函すうゆびてき实现带来りょう大麻たいま烦。

一个类的虚基类指针指向的虚基类表的首个条目,该条目的もくてき值是きょもと类表ゆび所在しょざいてきいた该类てき实例てきないそんくびてきへんうつり值。そく&(obj.vbtbl) - &objきょもと类第2、だい3、... 个条为该类的さいひだりきょ继承ちち类、ひだりきょ继承ちち类、...まとないそんしょう对于きょもと类表ゆび自身じしんそく &(obj.vbtbl)てきへんうつり值。

如果一个类同时有虚继承的父类与祖父类,则虚祖父そふ类放ざいきょちち类前めん

另外需要じゅよう注意ちゅういてき,类的きょ函数かんすうひょうてきだい一项之前的项(そく*(obj.vftbl-1))为最派生はせい类实れいてきないそんくびいたとうぜんきょ函数かんすうひょうゆび针的へんうつり值,そくmostDerivedObj-obj.vftbl。派生はせい类的きょ函数かんすうくつがえ盖基类的きょ函数かんすう时,ざいもと类的きょ函数かんすうひょうてき对应条目じょうもくうつしいれてきいち个“桩”(thunk)函数かんすうてき入口いりくち,以调せいthisゆび指向しこういた派生はせい类实れいてきさい调用派生はせい类的对应てききょ函数かんすうれい如:this -= offset; call DerivedClass:virtFunc;

きょ继承てき应用:不可ふか派生はせいてきfinally类

编辑

一个类如果不希望被继承,类似于Javaちゅうてき具有ぐゆうfinallyせい质的类,这在C++ちゅう以用きょ继承らい实现:

template<typename T> class MakeFinally{
   private:
       MakeFinally(){};//ただゆうMakeFinallyてきとも类才以构づくりMakeFinally
       ~MakeFinally(){};
   friend T;
};

class MyClass:public virtual  MakeFinally<MyClass>{};//MyClass不可ふか派生はせい

//よし于虚继承,所以ゆえんDよう直接ちょくせつ负责构造MakeFinally类,从而导致编译报错,所以ゆえんDさく派生はせい类是合法ごうほうてき
class D: public MyClass{};
//另外,如果D类没ゆう实例对象,そくぼつゆう使用しよう,实际じょうD类是编译ゆるがせりゃく掉而报错


int main()
{
MyClass var1;
// D var2;  //这一行编译将导致错误,いん为D类的だま认构づくり函数かんすう合法ごうほう
}

まいり

编辑

态 (计算つくえ科学かがく)

引用いんよう

编辑
  1. ^ Andrei Milea. Solving the Diamond Problem with Virtual Inheritance. http://www.cprogramming.com/: Cprogramming.com. [2010-03-08]. (原始げんし内容ないようそん于2021-03-04). 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. ^ Ralph McArdell. C++/What is virtual inheritance?. http://en.allexperts.com/: All Experts. 2004-02-14 [2010-03-08]. (原始げんし内容ないようそん档于2010-01-10). 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.