0

有人可以向我解释一下粗体部分吗?

我不明白为什么 *__vptr 它位于类的 Base 部分,并且 dPtr 可以访问该指针,可以突然指向 D1 虚拟表而不是 Base 虚拟表!我读过一些文章,看了一些资料,但仍然感到困惑。

class Base
{
public:
    virtual void function1() {};
    virtual void function2() {};
};
 
class D1: public Base
{
public:
    virtual void function1() {};
};
 
class D2: public Base
{
public:
    virtual void function2() {};
};
int main()
{
    D1 d1;
    Base *dPtr = &d1;
 
    return 0;
}

请注意,因为 dPtr 是一个基指针,它只指向 d1 的基部分。但是,还要注意 *__vptr 位于类的 Base 部分,因此 dPtr 可以访问该指针。最后,注意 dPtr->__vptr 指向 D1 虚拟表!因此,即使 dPtr 是 Base 类型,它仍然可以访问 D1 的虚拟表(通过 __vptr)。

来源:https ://www.learncpp.com/cpp-tutorial/125-the-virtual-table/comment-page-6/#comment-484189

4

1 回答 1

2

要了解,您需要了解 C++ 的实现不是 C++ 的定义。

C++ 由抽象机器的行为定义。该抽象机的行为由标准定义,兼容的 C++ 编译器必须编译程序才能像在该抽象机上运行一样运行。

该抽象机器所做的事情的规则是基于真实计算机和 C++ 和 C 程序的真实实现的启发和基础。

因此,当您谈论“虚拟函数表”时,您谈论的是 C++ 对虚拟方法所做的一种常见实现。这种常见的实现没有定义 C++ 的行为方式,将两者混合起来可能会导致问题。


话虽这么说,C++ 的虚拟方法基于在 C 中做基本完全相同的事情。如果重新实现它,它可以帮助勾勒出虚拟方法和继承在 C++ 中的工作方式。(这有实际用途,因为这样做可以让您制作自定义对象模型,而自定义对象模型让您可以比 C++ 对象模型更有效地完成某些事情)。

struct Bob_vtable {
  void(*print)(Bob const*) = 0;
};

struct Bob {
  Bob_vtable const* vtable = 0;
  int x = 0;

  // glue code to dispatch to vtable:
  void print() const {
    return vtable->print(this);
  }
  
  // implementation of Bob::print:
  static void print_impl( Bob const* self ) {
    std::cout << self->x;
  }

  // vtable helpers:
  static Bob_vtable make_vtable() {
    return { &Bob::print_impl };
  }
  static Bob_vtable const* get_vtable() {
    static const Bob_vtable retval = make_vtable();
    return &retval;
  }
  Bob():vtable(get_vtable()) {}
};

这是一个非常简单的、没有继承的、Bob具有单个虚拟方法的类的实现print。大致对应:

class Bob {
public:
  int x = 0;
  virtual void print() const { std::cout << x; }
};

你会明白为什么为你编写所有这些胶水代码是件好事。

当你这样做时:

class Alice : public Bob {
public:
  int y = 0;
  void print() const override { std::cout << x << "," << y; }
};

“手动实施”看起来像:

struct Alice : Bob {
  int y = 0;

  // no print glue code needed(!)

  // implementation of Alice::print:
  static void print_impl( Bob const* bobself ) {
    Alice const* self = static_cast<Alice const*>(bobself);
    std::cout << self->x << "," << self->y;
  }

  static Bob_vtable make_vtable() {
    Bob_vtable bob_version = Bob::make_vtable();
    bob_version.print = &Alice::print_impl;
    return bob_version;
  }
  static Bob_vtable const* get_vtable() {
    static const Bob_vtable retval = make_vtable();
    return &retval;
  }

  Alice():Bob() {
    // after constructing Bob, replace the vtable with ours:
    vtable = get_vtable();
  }
};

你有它。

看看这里发生了什么:

Alice a;
a.print(std::cout);

现在,a.print实际上调用Bob::print,因为Alice没有print方法。

Bob.print做这个:

  void print() const {
    return vtable->print(this);
  }

它获取该对象实例的 vtable 指针,并在其中调用 print 函数。

什么是类型对象的 vtable 指针Alice?看Alice构造函数。

首先它是默认构造Bob(设置vtable为指向Bob的 vtable),然后它会这样做:

    vtable = get_vtable();

此调用get_vtable调用Alice::get_vtable

    static const Bob_vtable retval = make_vtable();
    return &retval;

依次调用Alice::make_vtable

    Bob_vtable bob_version = Bob::make_vtable();
    bob_version.print = &Alice::print_impl;
    return bob_version;

首先调用Bob's make_vtable,然后替换.printAlice::print_impl.

所以Bob::print调用vtable->print(this), 即Alice::print_impl(this), 执行以下操作:

    Alice const* self = static_cast<Alice const*>(bobself);
    std::cout << self->x << "," << self->y;

虽然此时this是 a Bob const*,它指向一个Alice对象,所以这static_cast是有效的。

所以我们打印xyAlice.

现在,这里Alice的 vtable 类型是Bob_vtable因为她没有添加任何新方法。如果她添加新方法,她将拥有一个Alice_vtablethat 继承自Bob_vtable,并且必须static_cast<Alice_vtable const*>(vtable)访问它们。

这并不完全是 “在幕后”所做的,但它在逻辑上与我可以“即兴发挥”所写的一样。有很多不同的细节,比如vtable中函数的调用约定不同,内存中vtable的格式不匹配等等。


现在,在“手动实现”中,我确实使用了继承。所以那不是C;但是“手动实现”中的继承并没有做任何面向对象的事情。

struct A {int x;}; 
struct B:A{int y;};

只是在做

struct A {
  int x;
}; 
struct B {
  A base;
  int y;
};

顶部有一点句法闪光。

中实施(以及人们这样做)几乎是 1:1 的。您会将方法移出类,调用它们void Bob_print(Bob const*)而不是void Bob::print() const. 你会使用struct Alice { Bob base; int y; }而不是struct Alice:Bob{ int y; };. 但区别几乎完全是语法,而不是其他任何东西。

时,存在基于 OO 的 C,而 C++ 的首要目标之一是能够编写带有类的 C,而无需编写上述所有样板。

现在,C++ 的对象模型不需要上述实现。事实上,依赖上述实现可能会导致程序格式错误或未定义的行为。但是理解实现 C++ 对象模型的一种可能方式有一些用处。另外,一旦你知道如何实现 C++ 的对象模型,你就可以在 C++ 中使用不同的对象模型。

请注意,在现代 C++ 中,我会在上面使用更多模板来删除一些样板。作为实际用途,我使用了类似的技术来std::any使用鸭子类型的虚拟方法来实现增强的 's。

结果是您可以获得以下语法:

auto print = poly_method<void(Self const*, std::ostream&)>{
  [](auto const*self, std::ostream& os){ os << *self; }
};
poly_any<&print> x = 7;
x->*print(std::cout);

(不要在家里尝试这个)。

于 2020-12-04T15:53:15.963 回答