简体   繁体   English

虚函数和vtable是如何实现的?

[英]How are virtual functions and vtable implemented?

We all know what virtual functions are in C++, but how are they implemented at a deep level?我们都知道 C++ 中的虚函数是什么,但是它们是如何在深层次实现的呢?

Can the vtable be modified or even directly accessed at runtime?可以在运行时修改甚至直接访问 vtable 吗?

Does the vtable exist for all classes, or only those that have at least one virtual function? vtable 是否适用于所有类,或者只有那些至少具有一个虚函数的类?

Do abstract classes simply have a NULL for the function pointer of at least one entry?抽象类是否只为至少一个条目的函数指针设置了一个 NULL?

Does having a single virtual function slow down the whole class?使用单个虚函数会减慢整个课程的速度吗? Or only the call to the function that is virtual?还是只调用虚拟函数? And does the speed get affected if the virtual function is actually overwritten or not, or does this have no effect so long as it is virtual.如果虚函数实际上被覆盖与否,速度是否会受到影响,或者只要它是虚函数就没有影响。

How are virtual functions implemented at a deep level?虚函数是如何深层次实现的?

From "Virtual Functions in C++" :来自“C++中的虚拟函数”

Whenever a program has a virtual function declared, av - table is constructed for the class.每当程序声明了一个虚函数时,就会为该类构造 av-table。 The v-table consists of addresses to the virtual functions for classes that contain one or more virtual functions. v-table 由包含一个或多个虚函数的类的虚函数地址组成。 The object of the class containing the virtual function contains a virtual pointer that points to the base address of the virtual table in memory.包含虚拟函数的类的对象包含一个虚拟指针,指向内存中虚拟表的基地址。 Whenever there is a virtual function call, the v-table is used to resolve to the function address.每当有虚函数调用时,都会使用 v-table 来解析函数地址。 An object of the class that contains one or more virtual functions contains a virtual pointer called the vptr at the very beginning of the object in the memory.包含一个或多个虚函数的类的对象在内存中对象的最开始处包含一个称为 vptr 的虚拟指针。 Hence the size of the object in this case increases by the size of the pointer.因此,在这种情况下,对象的大小增加了指针的大小。 This vptr contains the base address of the virtual table in memory.这个 vptr 包含虚拟表在内存中的基地址。 Note that virtual tables are class specific, ie, there is only one virtual table for a class irrespective of the number of virtual functions it contains.请注意,虚拟表是特定于类的,即,一个类只有一个虚拟表,而不管它包含多少个虚拟函数。 This virtual table in turn contains the base addresses of one or more virtual functions of the class.该虚拟表又包含类的一个或多个虚拟函数的基地址。 At the time when a virtual function is called on an object, the vptr of that object provides the base address of the virtual table for that class in memory.在对象上调用虚拟函数时,该对象的 vptr 提供该类在内存中的虚拟表的基地址。 This table is used to resolve the function call as it contains the addresses of all the virtual functions of that class.该表用于解析函数调用,因为它包含该类的所有虚函数的地址。 This is how dynamic binding is resolved during a virtual function call.这就是在虚函数调用期间解析动态绑定的方式。

Can the vtable be modified or even directly accessed at runtime?可以在运行时修改甚至直接访问 vtable 吗?

Universally, I believe the answer is "no".总的来说,我相信答案是“不”。 You could do some memory mangling to find the vtable but you still wouldn't know what the function signature looks like to call it.您可以进行一些内存重整以找到 vtable,但您仍然不知道调用它的函数签名是什么样的。 Anything that you would want to achieve with this ability (that the language supports) should be possible without access to the vtable directly or modifying it at runtime.您希望通过这种能力(语言支持的)实现的任何事情都可以在不直接访问 vtable 或在运行时修改它的情况下实现。 Also note, the C++ language spec does not specify that vtables are required - however that is how most compilers implement virtual functions.还要注意,C++ 语言规范没有指定需要 vtables - 然而大多数编译器都是这样实现虚函数的。

Does the vtable exist for all objects, or only those that have at least one virtual function? vtable 是对所有对象都存在,还是只对那些至少具有一个虚函数的对象存在?

I believe the answer here is "it depends on the implementation" since the spec doesn't require vtables in the first place.相信这里的答案是“这取决于实现”,因为规范首先不需要 vtables。 However, in practice, I believe all modern compilers only create a vtable if a class has at least 1 virtual function.但是,在实践中,我相信所有现代编译器仅在类至少具有 1 个虚函数时才创建 vtable。 There is a space overhead associated with the vtable and a time overhead associated with calling a virtual function vs a non-virtual function.存在与 vtable 相关的空间开销和与调用虚函数与​​非虚函数相关的时间开销。

Do abstract classes simply have a NULL for the function pointer of at least one entry?抽象类是否只为至少一个条目的函数指针设置了一个 NULL?

The answer is it is unspecified by the language spec so it depends on the implementation.答案是语言规范未指定它,因此它取决于实现。 Calling the pure virtual function results in undefined behavior if it is not defined (which it usually isn't) (ISO/IEC 14882:2003 10.4-2).如果未定义(通常未定义),则调用纯虚函数会导致未定义行为(ISO/IEC 14882:2003 10.4-2)。 In practice it does allocate a slot in the vtable for the function but does not assign an address to it.实际上,它确实在 vtable 中为函数分配了一个插槽,但没有为其分配地址。 This leaves the vtable incomplete which requires the derived classes to implement the function and complete the vtable.这使得虚表不完整,需要派生类来实现功能并完成虚表。 Some implementations do simply place a NULL pointer in the vtable entry;一些实现只是在 vtable 条目中放置一个 NULL 指针; other implementations place a pointer to a dummy method that does something similar to an assertion.其他实现放置一个指向执行类似于断言的虚拟方法的指针。

Note that an abstract class can define an implementation for a pure virtual function, but that function can only be called with a qualified-id syntax (ie., fully specifying the class in the method name, similar to calling a base class method from a derived class).请注意,抽象类可以为纯虚函数定义实现,但该函数只能使用限定 id 语法调用(即,在方法名称中完全指定类,类似于从派生类)。 This is done to provide an easy to use default implementation, while still requiring that a derived class provide an override.这样做是为了提供易于使用的默认实现,同时仍然需要派生类提供覆盖。

Does having a single virtual function slow down the whole class or only the call to the function that is virtual?使用单个虚函数会减慢整个类的速度还是只会减慢对虚函数的调用速度?

This is getting to the edge of my knowledge, so someone please help me out here if I'm wrong!这是我知识的边缘,所以如果我错了,请有人在这里帮助我!

I believe that only the functions that are virtual in the class experience the time performance hit related to calling a virtual function vs. a non-virtual function.相信只有类中的虚拟函数才会经历与调用虚拟函数与非虚拟函数相关的时间性能损失。 The space overhead for the class is there either way.无论哪种方式,类的空间开销都存在。 Note that if there is a vtable, there is only 1 per class , not one per object .请注意,如果有一个 vtable,则每个class只有 1 个,而不是每个object一个。

Does the speed get affected if the virtual function is actually overridden or not, or does this have no effect so long as it is virtual?如果虚函数实际上是否被覆盖,速度是否会受到影响,或者只要它是虚函数就没有影响?

I don't believe the execution time of a virtual function that is overridden decreases compared to calling the base virtual function.我不相信与调用基本虚函数相比,被覆盖的虚函数的执行时间会减少。 However, there is an additional space overhead for the class associated with defining another vtable for the derived class vs the base class.但是,与为派生类与基类定义另一个 vtable 相关联的类有额外的空间开销。

Additional Resources:其他资源:

http://www.codersource.net/published/view/325/virtual_functions_in.aspx (via way back machine) http://www.codersource.net/published/view/325/virtual_functions_in.aspx (通过返回机器)
http://en.wikipedia.org/wiki/Virtual_table http://en.wikipedia.org/wiki/Virtual_table
http://www.codesourcery.com/public/cxx-abi/abi.html#vtable http://www.codesourcery.com/public/cxx-abi/abi.html#vtable

  • Can the vtable be modified or even directly accessed at runtime?可以在运行时修改甚至直接访问 vtable 吗?

Not portably, but if you don't mind dirty tricks, sure!不可移植,但如果你不介意肮脏的把戏,当然!

WARNING : This technique is not recommended for use by children, adults under the age of 969 , or small furry creatures from Alpha Centauri.警告:不建议儿童、 969岁以下的成人或半人马座阿尔法星的小型毛茸茸的生物使用此技术。 Side effects may include demons which fly out of your nose , the abrupt appearence of Yog-Sothoth as a required approver on all subsequent code reviews, or the retroactive addition of IHuman::PlayPiano() to all existing instances]副作用可能包括从你的鼻子里飞出来的恶魔, Yog-Sothoth突然出现作为所有后续代码审查所需的批准者,或者向所有现有实例追溯添加IHuman::PlayPiano() ]

In most compilers I've seen, the vtbl * is the first 4 bytes of the object, and the vtbl contents are simply an array of member pointers there (generally in the order they were declared, with the base class's first).在我见过的大多数编译器中, vtbl * 是对象的前 4 个字节,而 vtbl 内容只是那里的成员指针数组(通常按照它们的声明顺序,基类的第一个)。 There are of course other possible layouts, but that's what I've generally observed.当然还有其他可能的布局,但这是我通常观察到的。

class A {
  public:
  virtual int f1() = 0;
};
class B : public A {
  public:
  virtual int f1() { return 1; }
  virtual int f2() { return 2; }
};
class C : public A {
  public:
  virtual int f1() { return -1; }
  virtual int f2() { return -2; }
};

A *x = new B;
A *y = new C;
A *z = new C;

Now to pull some shenanigans...现在拉一些恶作剧......

Changing class at runtime:在运行时更改类:

std::swap(*(void **)x, *(void **)y);
// Now x is a C, and y is a B! Hope they used the same layout of members!

Replacing a method for all instances (monkeypatching a class)为所有实例替换一个方法(monkeypatching 一个类)

This one's a little trickier, since the vtbl itself is probably in read-only memory.这个有点棘手,因为 vtbl 本身可能在只读内存中。

int f3(A*) { return 0; }

mprotect(*(void **)x,8,PROT_READ|PROT_WRITE|PROT_EXEC);
// Or VirtualProtect on win32; this part's very OS-specific
(*(int (***)(A *)x)[0] = f3;
// Now C::f1() returns 0 (remember we made x into a C above)
// so x->f1() and z->f1() both return 0

The latter is rather likely to make virus-checkers and the link wake up and take notice, due to the mprotect manipulations.由于 mprotect 操作,后者很可能使病毒检查程序和链接被唤醒并引起注意。 In a process using the NX bit it may well fail.在使用 NX 位的过程中,它很可能会失败。

Does having a single virtual function slow down the whole class?使用单个虚函数会减慢整个课程的速度吗?

Or only the call to the function that is virtual?还是只调用虚拟函数? And does the speed get affected if the virtual function is actually overwritten or not, or does this have no effect so long as it is virtual.如果虚函数实际上被覆盖与否,速度是否会受到影响,或者只要它是虚函数就没有影响。

Having virtual functions slows down the whole class insofar as one more item of data has to be initialized, copied, … when dealing with an object of such a class.使用虚函数会减慢整个类的速度,因为在处理这样一个类的对象时,还必须初始化、复制一项数据。 For a class with half a dozen members or so, the difference should be neglible.对于一个有六人左右的班级来说,差异应该可以忽略不计。 For a class which just contains a single char member, or no members at all, the difference might be notable.对于只包含单个char成员或根本没有成员的类,差异可能会很明显。

Apart from that, it is important to note that not every call to a virtual function is a virtual function call.除此之外,重要的是要注意并非对虚函数的每次调用都是虚函数调用。 If you have an object of a known type, the compiler can emit code for a normal function invocation, and can even inline said function if it feels like it.如果您有一个已知类型的对象,编译器可以为正常的函数调用发出代码,甚至可以根据需要内联该函数。 It's only when you do polymorphic calls, via a pointer or reference which might point at an object of the base class or at an object of some derived class, that you need the vtable indirection and pay for it in terms of performance.只有当您通过可能指向基类的对象或某些派生类的对象的指针或引用进行多态调用时,您才需要 vtable 间接并在性能方面付出代价。

struct Foo { virtual ~Foo(); virtual int a() { return 1; } };
struct Bar: public Foo { int a() { return 2; } };
void f(Foo& arg) {
  Foo x; x.a(); // non-virtual: always calls Foo::a()
  Bar y; y.a(); // non-virtual: always calls Bar::a()
  arg.a();      // virtual: must dispatch via vtable
  Foo z = arg;  // copy constructor Foo::Foo(const Foo&) will convert to Foo
  z.a();        // non-virtual Foo::a, since z is a Foo, even if arg was not
}

The steps the hardware has to take are essentially the same, no matter whether the function is overwritten or not.无论函数是否被覆盖,硬件必须采取的步骤本质上是相同的。 The address of the vtable is read from the object, the function pointer retrieved from the appropriate slot, and the function called by pointer. vtable 的地址是从对象中读取的,从适当的槽中检索的函数指针,以及指针调用的函数。 In terms of actual performance, branch predictions might have some impact.就实际性能而言,分支预测可能会产生一些影响。 So for example, if most of your objects refer to the same implementation of a given virtual function, then there is some chance that the branch predictor will correctly predict which function to call even before the pointer has been retrieved.因此,例如,如果您的大多数对象都引用给定虚拟函数的相同实现,那么即使在检索指针之前,分支预测器也有可能正确预测要调用的函数。 But it doesn't matter which function is the common one: it could be most objects delegating to the non-overwritten base case, or most objects belonging to the same subclass and therefore delegating to the same overwritten case.但哪个函数是通用函数并不重要:它可能是委托给未覆盖基本情况的大多数对象,或者属于同一子类并因此委托给相同覆盖情况的大多数对象。

how are they implemented at a deep level?它们是如何在深层次实施的?

I like the idea of jheriko to demonstrate this using a mock implementation.我喜欢 jheriko 使用模拟实现来演示这一点的想法。 But I'd use C to implement something akin to the code above, so that the low level is more easily seen.但是我会使用 C 来实现类似于上面代码的东西,以便更容易看到低级别。

parent class Foo父类 Foo

typedef struct Foo_t Foo;   // forward declaration
struct slotsFoo {           // list all virtual functions of Foo
  const void *parentVtable; // (single) inheritance
  void (*destructor)(Foo*); // virtual destructor Foo::~Foo
  int (*a)(Foo*);           // virtual function Foo::a
};
struct Foo_t {                      // class Foo
  const struct slotsFoo* vtable;    // each instance points to vtable
};
void destructFoo(Foo* self) { }     // Foo::~Foo
int aFoo(Foo* self) { return 1; }   // Foo::a()
const struct slotsFoo vtableFoo = { // only one constant table
  0,                                // no parent class
  destructFoo,
  aFoo
};
void constructFoo(Foo* self) {      // Foo::Foo()
  self->vtable = &vtableFoo;        // object points to class vtable
}
void copyConstructFoo(Foo* self,
                      Foo* other) { // Foo::Foo(const Foo&)
  self->vtable = &vtableFoo;        // don't copy from other!
}

derived class Bar派生类 Bar

typedef struct Bar_t {              // class Bar
  Foo base;                         // inherit all members of Foo
} Bar;
void destructBar(Bar* self) { }     // Bar::~Bar
int aBar(Bar* self) { return 2; }   // Bar::a()
const struct slotsFoo vtableBar = { // one more constant table
  &vtableFoo,                       // can dynamic_cast to Foo
  (void(*)(Foo*)) destructBar,      // must cast type to avoid errors
  (int(*)(Foo*)) aBar
};
void constructBar(Bar* self) {      // Bar::Bar()
  self->base.vtable = &vtableBar;   // point to Bar vtable
}

function f performing virtual function call函数 f 执行虚函数调用

void f(Foo* arg) {                  // same functionality as above
  Foo x; constructFoo(&x); aFoo(&x);
  Bar y; constructBar(&y); aBar(&y);
  arg->vtable->a(arg);              // virtual function call
  Foo z; copyConstructFoo(&z, arg);
  aFoo(&z);
  destructFoo(&z);
  destructBar(&y);
  destructFoo(&x);
}

So you can see, a vtable is just a static block in memory, mostly containing function pointers.所以你可以看到,一个 vtable 只是内存中的一个静态块,主要包含函数指针。 Every object of a polymorphic class will point to the vtable corresponding to its dynamic type.多态类的每个对象都将指向与其动态类型相对应的虚表。 This also makes the connection between RTTI and virtual functions clearer: you can check what type a class is simply by looking at what vtable it points at.这也使得 RTTI 和虚函数之间的联系更加清晰:你可以通过查看它指向的 vtable 来检查一个类是什么类型。 The above is simplified in many ways, like eg multiple inheritance, but the general concept is sound.以上在许多方面进行了简化,例如多重继承,但一般概念是合理的。

If arg is of type Foo* and you take arg->vtable , but is actually an object of type Bar , then you still get the correct address of the vtable .如果argFoo*类型并且您使用arg->vtable ,但实际上是Bar类型的对象,那么您仍然会获得vtable的正确地址。 That's because the vtable is always the first element at the address of the object, no matter whether it's called vtable or base.vtable in a correctly-typed expression.这是因为vtable始终是对象地址处的第一个元素,无论它在正确类型的表达式中被称为vtable还是base.vtable

Here is a runnable manual implementation of virtual table in modern C++.这是现代 C++ 中虚拟表的可运行手动实现。 It has well-defined semantics, no hacks and no void* .它具有明确定义的语义,没有黑客攻击,也没有void*

Note: .* and ->* are different operators than * and -> .注意: .*->*是不同于*->运算符。 Member function pointers work differently.成员函数指针的工作方式不同。

#include <iostream>
#include <vector>
#include <memory>

struct vtable; // forward declare, we need just name

class animal
{
public:
    const std::string& get_name() const { return name; }

    // these will be abstract
    bool has_tail() const;
    bool has_wings() const;
    void sound() const;

protected: // we do not want animals to be created directly
    animal(const vtable* vtable_ptr, std::string name)
    : vtable_ptr(vtable_ptr), name(std::move(name)) { }

private:
    friend vtable; // just in case for non-public methods

    const vtable* const vtable_ptr;
    std::string name;
};

class cat : public animal
{
public:
    cat(std::string name);

    // functions to bind dynamically
    bool has_tail() const { return true; }
    bool has_wings() const { return false; }
    void sound() const
    {
        std::cout << get_name() << " does meow\n"; 
    }
};

class dog : public animal
{
public:
    dog(std::string name);

    // functions to bind dynamically
    bool has_tail() const { return true; }
    bool has_wings() const { return false; }
    void sound() const
    {
        std::cout << get_name() << " does whoof\n"; 
    }
};

class parrot : public animal
{
public:
    parrot(std::string name);

    // functions to bind dynamically
    bool has_tail() const { return false; }
    bool has_wings() const { return true; }
    void sound() const
    {
        std::cout << get_name() << " does crrra\n"; 
    }
};

// now the magic - pointers to member functions!
struct vtable
{
    bool (animal::* const has_tail)() const;
    bool (animal::* const has_wings)() const;
    void (animal::* const sound)() const;

    // constructor
    vtable (
        bool (animal::* const has_tail)() const,
        bool (animal::* const has_wings)() const,
        void (animal::* const sound)() const
    ) : has_tail(has_tail), has_wings(has_wings), sound(sound) { }
};

// global vtable objects
const vtable vtable_cat(
    static_cast<bool (animal::*)() const>(&cat::has_tail),
    static_cast<bool (animal::*)() const>(&cat::has_wings),
    static_cast<void (animal::*)() const>(&cat::sound));
const vtable vtable_dog(
    static_cast<bool (animal::*)() const>(&dog::has_tail),
    static_cast<bool (animal::*)() const>(&dog::has_wings),
    static_cast<void (animal::*)() const>(&dog::sound));
const vtable vtable_parrot(
    static_cast<bool (animal::*)() const>(&parrot::has_tail),
    static_cast<bool (animal::*)() const>(&parrot::has_wings),
    static_cast<void (animal::*)() const>(&parrot::sound));

// set vtable pointers in constructors
cat::cat(std::string name) : animal(&vtable_cat, std::move(name)) { }
dog::dog(std::string name) : animal(&vtable_dog, std::move(name)) { }
parrot::parrot(std::string name) : animal(&vtable_parrot, std::move(name)) { }

// implement dynamic dispatch
bool animal::has_tail() const
{
    return (this->*(vtable_ptr->has_tail))();
}

bool animal::has_wings() const
{
    return (this->*(vtable_ptr->has_wings))();
}

void animal::sound() const
{
    (this->*(vtable_ptr->sound))();
}

int main()
{
    std::vector<std::unique_ptr<animal>> animals;
    animals.push_back(std::make_unique<cat>("grumpy"));
    animals.push_back(std::make_unique<cat>("nyan"));
    animals.push_back(std::make_unique<dog>("doge"));
    animals.push_back(std::make_unique<parrot>("party"));

    for (const auto& a : animals)
        a->sound();

    // note: destructors are not dispatched virtually
}

通常带有一个 VTable,一个指向函数的指针数组。

You can recreate the functionality of virtual functions in C++ using function pointers as members of a class and static functions as the implementations, or using pointer to member functions and member functions for the implementations.您可以使用函数指针作为类的成员并使用静态函数作为实现,或者使用指向成员函数和成员函数的指针作为实现,在 C++ 中重新创建虚拟函数的功能。 There are only notational advantages between the two methods... in fact virtual function calls are just a notational convenience themselves.这两种方法之间只有符号上的优势……事实上,虚函数调用本身只是一种符号上的便利。 In fact inheritance is just a notational convenience... it can all be implemented without using the language features for inheritance.事实上,继承只是一种符号上的方便……它可以在不使用继承的语言特性的情况下实现。 :) :)

The below is crap untested, probably buggy code, but hopefully demonstrates the idea.下面是未经测试的废话,可能是有问题的代码,但希望能展示这个想法。

eg例如

class Foo
{
protected:
 void(*)(Foo*) MyFunc;
public:
 Foo() { MyFunc = 0; }
 void ReplciatedVirtualFunctionCall()
 {
  MyFunc(*this);
 }
...
};

class Bar : public Foo
{
private:
 static void impl1(Foo* f)
 {
  ...
 }
public:
 Bar() { MyFunc = impl1; }
...
};

class Baz : public Foo
{
private:
 static void impl2(Foo* f)
 {
  ...
 }
public:
 Baz() { MyFunc = impl2; }
...
};

I'll try to make it simple :)我会尽量让它简单:)

We all know what virtual functions are in C++, but how are they implemented at a deep level?我们都知道 C++ 中的虚函数是什么,但是它们是如何在深层次实现的呢?

This is an array with pointers to functions, which are implementations of a particular virtual function.这是一个带有函数指针的数组,函数是特定虚函数的实现。 An index in this array represents particular index of a virtual function defined for a class.此数组中的索引表示为类定义的虚函数的特定索引。 This includes pure virtual functions.这包括纯虚函数。

When a polymorphic class derives from another polymorphic class, we may have the following situations:当一个多态类派生自另一个多态类时,我们可能会遇到以下情况:

  • The deriving class does not add new virtual functions nor overrides any.派生类不会添加新的虚函数,也不会覆盖任何虚函数。 In this case this class shares the vtable with the base class.在这种情况下,此类与基类共享 vtable。
  • The deriving class adds and overrides virtual methods.派生类添加并覆盖虚拟方法。 In this case it gets its own vtable, where the added virtual functions have index starting past the last derived one.在这种情况下,它有自己的 vtable,其中添加的虚函数的索引从最后一个派生的开始。
  • Multiple polymorphic classes in the inheritance.继承中的多个多态类。 In this case we have an index-shift between second and next bases and the index of it in the derived class在这种情况下,我们在第二个和下一个基数之间有一个索引移位,以及它在派生类中的索引

Can the vtable be modified or even directly accessed at runtime?可以在运行时修改甚至直接访问 vtable 吗?

Not standard way - there's no API to access them.不是标准方式 - 没有 API 可以访问它们。 Compilers may have some extensions or private APIs to access them, but that may be only an extension.编译器可能有一些扩展或私有 API 来访问它们,但这可能只是一个扩展。

Does the vtable exist for all classes, or only those that have at least one virtual function? vtable 是否适用于所有类,或者只有那些至少具有一个虚函数的类?

Only those that have at least one virtual function (be it even destructor) or derive at least one class that has its vtable ("is polymorphic").只有那些至少具有一个虚函数(甚至是析构函数)或派生至少一个具有其 vtable 的类(“是多态的”)。

Do abstract classes simply have a NULL for the function pointer of at least one entry?抽象类是否只为至少一个条目的函数指针设置了一个 NULL?

That's a possible implementation, but rather not practiced.这是一个可能的实现,但没有实践过。 Instead there is usually a function that prints something like "pure virtual function called" and does abort() .相反,通常有一个函数会打印类似“调用的纯虚函数”并执行abort() The call to that may occur if you try to call the abstract method in the constructor or destructor.如果您尝试在构造函数或析构函数中调用抽象方法,则可能会调用该方法。

Does having a single virtual function slow down the whole class?使用单个虚函数会减慢整个课程的速度吗? Or only the call to the function that is virtual?还是只调用虚拟函数? And does the speed get affected if the virtual function is actually overwritten or not, or does this have no effect so long as it is virtual.如果虚函数实际上被覆盖与否,速度是否会受到影响,或者只要它是虚函数就没有影响。

The slowdown is only dependent on whether the call is resolved as direct call or as a virtual call.减速仅取决于呼叫是解析为直接呼叫还是虚拟呼叫。 And nothing else matters.其他一切都不重要。 :) :)

If you call a virtual function through a pointer or reference to an object, then it will be always implemented as virtual call - because the compiler can never know what kind of object will be assigned to this pointer in runtime, and whether it is of a class in which this method is overridden or not.如果你通过一个指针或对对象的引用来调用一个虚函数,那么它总是会被实现为虚调用——因为编译器永远无法知道在运行时会给这个指针分配什么样的对象,以及它是否属于是否覆盖此方法的类。 Only in two cases the compiler can resolve the call to a virtual function as a direct call:只有在两种情况下,编译器才能将虚函数调用解析为直接调用:

  • If you call the method through a value (a variable or result of a function that returns a value) - in this case the compiler has no doubts what the actual class of the object is, and can "hard-resolve" it at compile time.如果您通过一个值(一个返回值的函数的变量或结果)调用该方法 - 在这种情况下,编译器不会怀疑对象的实际类是什么,并且可以在编译时“硬解析”它.
  • If the virtual method is declared final in the class to which you have a pointer or reference through which you call it ( only in C++11 ).如果虚拟方法在您具有调用它的指针或引用的类中声明为final仅在 C++11 中)。 In this case compiler knows that this method cannot undergo any further overriding and it can only be the method from this class.在这种情况下,编译器知道此方法不能进行任何进一步的覆盖,并且它只能是此类中的方法。

Note though that virtual calls have only overhead of dereferencing two pointers.请注意,虚拟调用只有取消引用两个指针的开销。 Using RTTI (although only available for polymorphic classes) is slower than calling virtual methods, should you find a case to implement the same thing two such ways.使用 RTTI(虽然仅适用于多态类)比调用虚方法慢,如果您找到一个案例以两种方式实现相同的事情。 For example, defining virtual bool HasHoof() { return false; }例如,定义virtual bool HasHoof() { return false; } virtual bool HasHoof() { return false; } and then override only as bool Horse::HasHoof() { return true; } virtual bool HasHoof() { return false; }然后只覆盖为bool Horse::HasHoof() { return true; } bool Horse::HasHoof() { return true; } would provide you with ability to call if (anim->HasHoof()) that will be faster than trying if(dynamic_cast<Horse*>(anim)) . bool Horse::HasHoof() { return true; }会为您提供能力调用if (anim->HasHoof())这将是快于尝试if(dynamic_cast<Horse*>(anim)) This is because dynamic_cast has to walk through the class hierarchy in some cases even recursively to see if there can be built the path from the actual pointer type and the desired class type.这是因为dynamic_cast在某些情况下必须遍历类层次结构,甚至递归地查看是否可以从实际指针类型和所需的类类型构建路径。 While the virtual call is always the same - dereferencing two pointers.虽然虚拟调用总是相同的 - 取消引用两个指针。

This answer has been incorporated into the Community Wiki answer此答案已纳入社区 Wiki 答案

  • Do abstract classes simply have a NULL for the function pointer of at least one entry?抽象类是否只为至少一个条目的函数指针设置了一个 NULL?

The answer for that is that it is unspecified - calling the pure virtual function results in undefined behavior if it is not defined (which it usually isn't) (ISO/IEC 14882:2003 10.4-2).答案是它是未指定的 - 如果未定义(通常未定义),则调用纯虚函数会导致未定义的行为(ISO/IEC 14882:2003 10.4-2)。 Some implementations do simply place a NULL pointer in the vtable entry;一些实现只是在 vtable 条目中放置一个 NULL 指针; other implementations place a pointer to a dummy method that does something similar to an assertion.其他实现放置一个指向执行类似于断言的虚拟方法的指针。

Note that an abstract class can define an implementation for a pure virtual function, but that function can only be called with a qualified-id syntax (ie., fully specifying the class in the method name, similar to calling a base class method from a derived class).请注意,抽象类可以为纯虚函数定义实现,但该函数只能使用限定 id 语法调用(即,在方法名称中完全指定类,类似于从派生类)。 This is done to provide an easy to use default implementation, while still requiring that a derived class provide an override.这样做是为了提供易于使用的默认实现,同时仍然需要派生类提供覆盖。

Something not mentioned here in all these answers is that in case of multiple inheritance, where the base classes all have virtual methods.在所有这些答案中没有提到的是,在多重继承的情况下,基类都具有虚拟方法。 The inheriting class has multiple pointers to a vmt.继承类有多个指向 vmt 的指针。 The result is that the size of each instance of such an object is bigger.结果是这种对象的每个实例的大小都更大。 Everybody knows that a class with virtual methods has 4 bytes extra for the vmt, but in case of multiple inheritance it is for each base class that has virtual methods times 4. 4 being the size of the pointer.每个人都知道具有虚拟方法的类有 4 个额外的 vmt 字节,但在多重继承的情况下,它是每个基类的虚拟方法乘以 4。4 是指针的大小。

每个对象都有一个指向成员函数数组的 vtable 指针。

very cute proof of concept i made a bit earlier(to see if order of inheritence matters);我之前做了一个非常可爱的概念证明(看看继承顺序是否重要); let me know if your implementation of C++ actually rejects it(my version of gcc only gives a warning for assigning anonymous structs, but that's a bug), i'm curious.让我知道你的 C++ 实现是否真的拒绝它(我的 gcc 版本只给出了分配匿名结构的警告,但这是一个错误),我很好奇。

CCPolite.h : CCPolite.h :

#ifndef CCPOLITE_H
#define CCPOLITE_H

/* the vtable or interface */
typedef struct {
    void (*Greet)(void *);
    void (*Thank)(void *);
} ICCPolite;

/**
 * the actual "object" literal as C++ sees it; public variables be here too 
 * all CPolite objects use(are instances of) this struct's structure.
 */
typedef struct {
    ICCPolite *vtbl;
} CPolite;

#endif /* CCPOLITE_H */

CCPolite_constructor.h : CCPolite_constructor.h :

/** 
 * unconventionally include me after defining OBJECT_NAME to automate
 * static(allocation-less) construction.
 *
 * note: I assume CPOLITE_H is included; since if I use anonymous structs
 *     for each object, they become incompatible and cause compile time errors
 *     when trying to do stuff like assign, or pass functions.
 *     this is similar to how you can't pass void * to windows functions that
 *         take handles; these handles use anonymous structs to make 
 *         HWND/HANDLE/HINSTANCE/void*/etc not automatically convertible, and
 *         require a cast.
 */
#ifndef OBJECT_NAME
    #error CCPolite> constructor requires object name.
#endif

CPolite OBJECT_NAME = {
    &CCPolite_Vtbl
};

/* ensure no global scope pollution */
#undef OBJECT_NAME

main.c :主文件

#include <stdio.h>
#include "CCPolite.h"

// | A Greeter is capable of greeting; nothing else.
struct IGreeter
{
    virtual void Greet() = 0;
};

// | A Thanker is capable of thanking; nothing else.
struct IThanker
{
    virtual void Thank() = 0;
};

// | A Polite is something that implements both IGreeter and IThanker
// | Note that order of implementation DOES MATTER.
struct IPolite1 : public IGreeter, public IThanker{};
struct IPolite2 : public IThanker, public IGreeter{};

// | implementation if IPolite1; implements IGreeter BEFORE IThanker
struct CPolite1 : public IPolite1
{
    void Greet()
    {
        puts("hello!");
    }

    void Thank()
    {
        puts("thank you!");
    }
};

// | implementation if IPolite1; implements IThanker BEFORE IGreeter
struct CPolite2 : public IPolite2
{
    void Greet()
    {
        puts("hi!");
    }

    void Thank()
    {
        puts("ty!");
    }
};

// | imposter Polite's Greet implementation.
static void CCPolite_Greet(void *)
{
    puts("HI I AM C!!!!");
}

// | imposter Polite's Thank implementation.
static void CCPolite_Thank(void *)
{
    puts("THANK YOU, I AM C!!");
}

// | vtable of the imposter Polite.
ICCPolite CCPolite_Vtbl = {
    CCPolite_Thank,
    CCPolite_Greet    
};

CPolite CCPoliteObj = {
    &CCPolite_Vtbl
};

int main(int argc, char **argv)
{
    puts("\npart 1");
    CPolite1 o1;
    o1.Greet();
    o1.Thank();

    puts("\npart 2");    
    CPolite2 o2;    
    o2.Greet();
    o2.Thank();    

    puts("\npart 3");    
    CPolite1 *not1 = (CPolite1 *)&o2;
    CPolite2 *not2 = (CPolite2 *)&o1;
    not1->Greet();
    not1->Thank();
    not2->Greet();
    not2->Thank();

    puts("\npart 4");        
    CPolite1 *fake = (CPolite1 *)&CCPoliteObj;
    fake->Thank();
    fake->Greet();

    puts("\npart 5");        
    CPolite2 *fake2 = (CPolite2 *)fake;
    fake2->Thank();
    fake2->Greet();

    puts("\npart 6");        
    #define OBJECT_NAME fake3
    #include "CCPolite_constructor.h"
    fake = (CPolite1 *)&fake3;
    fake->Thank();
    fake->Greet();

    puts("\npart 7");        
    #define OBJECT_NAME fake4
    #include "CCPolite_constructor.h"
    fake2 = (CPolite2 *)&fake4;
    fake2->Thank();
    fake2->Greet();    

    return 0;
}

output:输出:

part 1
hello!
thank you!

part 2
hi!
ty!

part 3
ty!
hi!
thank you!
hello!

part 4
HI I AM C!!!!
THANK YOU, I AM C!!

part 5
THANK YOU, I AM C!!
HI I AM C!!!!

part 6
HI I AM C!!!!
THANK YOU, I AM C!!

part 7
THANK YOU, I AM C!!
HI I AM C!!!!

note since I am never allocating my fake object, there is no need to do any destruction;请注意,由于我从不分配我的假对象,因此无需进行任何销毁; destructors are automatically put at the end of scope of dynamically allocated objects to reclaim the memory of the object literal itself and the vtable pointer.析构函数会自动放在动态分配的对象范围的末尾,以回收对象文字本身和 vtable 指针的内存。

Burly's answers are correct here except for the question: Burly 的答案在这里是正确的,除了问题:

Do abstract classes simply have a NULL for the function pointer of at least one entry?抽象类是否只为至少一个条目的函数指针设置了一个 NULL?

The answer is that no virtual table is created at all for abstract classes.答案是根本没有为抽象类创建虚拟表。 There is no need since no objects of these classes can be created!没有必要,因为无法创建这些类的对象!

In other words if we have:换句话说,如果我们有:

class B { ~B() = 0; }; // Abstract Base class
class D : public B { ~D() {} }; // Concrete Derived class

D* pD = new D();
B* pB = pD;

The vtbl pointer accessed through pB will be the vtbl of class D. This is exactly how polymorphism is implemented.通过 pB 访问的 vtbl 指针将是 D 类的 vtbl。这正是多态的实现方式。 That is, how D methods are accessed through pB.即,如何通过 pB 访问 D 方法。 There is no need for a vtbl for class B. B 类不需要 vtbl。

In response to Mike's comment below...回应迈克在下面的评论......

If the B class in my description has a virtual method foo() that is not overridden by D and a virtual method bar() that is overridden, then D's vtbl will have a pointer to B's foo() and to its own bar() .如果我描述中的 B 类有一个未被 D 覆盖的虚拟方法foo()和一个被覆盖的虚拟方法bar() ,那么 D 的 vtbl 将有一个指向 B 的foo()和它自己的bar()的指针. There is still no vtbl created for B.仍然没有为 B 创建 vtbl。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM