【问题标题】:How does virtual base class works internally ? How the call to base methods resolved by compiler?虚拟基类如何在内部工作?编译器如何解决对基本方法的调用?
【发布时间】:2020-03-18 21:04:59
【问题描述】:

我了解虚函数,但我对虚基类及其内部工作方式感到困惑。有人可以帮忙吗?

class Base { };

Derived: virtual public Base { };

我知道有一个副本传递给派生类。

  • 但有人能解释一下这是如何在内部工作的吗?
  • 它与虚函数有何相似或不同之处?
  • 以及如何解决对基本方法的调用?

  • 如果派生类有虚基类和虚函数会怎样?

谢谢。

【问题讨论】:

  • 只要它的行为符合 C++ 标准的规定,你为什么要关心?
  • 它不是指定链接的副本。我的问题更具体到虚拟基类,以及它在内部与虚拟函数有何不同。 @NathanOliver-ReinstateMonica
  • 没有指定如何实现虚拟基类的内部细节,是编译器细节。 (就像虚函数的实现方式一样。)
  • 通常,它涉及一个额外的指针(如 vtable 指针),它指向一些元数据,包括一个偏移量,它将告诉当前this 的虚拟基体在哪里。找到基体后,它会像往常一样运行,您可以找到 vtable 指针等...不过,我真的不知道足够详细的信息来写答案。

标签: c++ c++11


【解决方案1】:

已引入虚拟基类以消除在 C++ 中的多重继承中出现的菱形问题。例如,

 #include <iostream>
#include <string>

using namespace std;

class Dog{
    string color;
public:
    Dog(string color): color(color)
    {cout<<color<<" Dog created"<<endl;}
};

class Rotweiler: public Dog{
    int age;
public:
    Rotweiler(int age, string color): Dog(color), age(age)
    {cout<<"Age "<<age<<" "<<color<<" Rotweiler created"<<endl;}
};

class Husky: public Dog{
    int teeth;
public:
    Husky(int teeth, string color): Dog(color), teeth (teeth)
    {cout<<"Teeth "<<teeth<<" "<<color<<" Husky created"<<endl;}
};

class MixBreed: public Rotweiler, public Husky{
public:
    MixBreed(int teeth, int age, string color): Rotweiler(age,color), Husky(teeth,color)
    {cout<<"Teeth "<<teeth<<" Age "<<age<<" "<<color <<" MixBreed created"<<endl;}
};

int main(){
    MixBreed puppy(24,4,"black");
}

此代码将导致,

black Dog created
Age 4 black Rotweiler created
black Dog created
Teeth 24 black Husky created
Teeth 24 Age 4 black MixBreed created

如您所见,我们打算创建一只狗和另外两只来自同一只狗的罗威纳犬和哈士奇犬,最后是从罗威纳犬和哈士奇犬衍生的混合品种。但是编译器创建了两个独立的 Dog 基类对象,而不是派生自同一个 Dog 对象。为了解决这个问题,我们可以使用虚拟继承。

  #include <iostream>
#include <string>

using namespace std;

class Dog{
    string color;
public:
    Dog(string color): color(color)
    {cout<<color<<" Dog created"<<endl;}
};

class Rotweiler: virtual public Dog{
    int age;
public:
    Rotweiler(int age, string color): Dog(color), age(age)
    {cout<<"Age "<<age<<" "<<color<<" Rotweiler created"<<endl;}
};

class Husky: virtual public Dog{
    int teeth;
public:
    Husky(int teeth, string color): Dog(color), teeth (teeth)
    {cout<<"Teeth "<<teeth<<" "<<color<<" Husky created"<<endl;}
};

class MixBreed: public Rotweiler, public Husky{
public:
    MixBreed(int teeth, int age, string color): Dog(color), Rotweiler(age,color), Husky(teeth,color) // notice Dog constructor called here
    {cout<<"Teeth "<<teeth<<" Age "<<age<<" "<<color <<" MixBreed created"<<endl;}
};

int main(){
    MixBreed puppy(24,4,"black");
}

现在请注意,Rottweiler 和 Husky 实际上继承了 Dog 类。我们的最终目标是避免创建重复的基类。这段代码会产生,

black Dog created
Age 4 black Rotweiler created
Teeth 24 black Husky created
Teeth 24 Age 4 black MixBreed created

但这里要注意的另一件事是 Dog 对象是由最派生的类 MixBreed 创建的(您可以看到 Dog() 构造函数被调用)。这是因为罗威纳犬和哈士奇犬实际上都继承了 Dog。上述代码编译时,派生最多的类是 MixBreed 应该调用 Dog 构造函数来创建唯一的 Dog 对象,否则会导致错误。来自 Rotweiler 和 Husky 的 Dog 构造函数调用都被忽略,因为 Dog 对象已经从 MixBreed 类创建。

  • 编译器一开始就创建了虚拟基类对象,以避免重复。
  • 现在派生最多的类负责创建虚拟基类对象
  • 如果您实例化 Rotweiler 或 Husky,它们将正常工作(它们将创建 Dog 对象)

另一个要记住的事实是所有继承一个虚拟基类的类都会有一个虚拟表,它们会通过一个指针指向同一个基类对象。因此基类的所有派生类都可以访问基类的行为。

【讨论】:

    猜你喜欢
    • 2019-05-24
    • 2015-07-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-30
    • 2017-05-05
    相关资源
    最近更新 更多