Как реализуются виртуальные функции и vtable? - PullRequest
99 голосов
/ 19 сентября 2008

Мы все знаем, что такое виртуальные функции в C ++, но как они реализуются на глубоком уровне?

Может ли vtable быть изменен или даже напрямую доступен во время выполнения?

Существует ли vtable для всех классов или только для тех, у которых есть хотя бы одна виртуальная функция?

У абстрактных классов просто NULL для указателя на функцию хотя бы одной записи?

Замедляет ли наличие одной виртуальной функции весь класс? Или только вызов функции, которая является виртуальной? И влияет ли на скорость, если виртуальная функция действительно перезаписана или нет, или она не оказывает влияния, пока она виртуальная.

Ответы [ 12 ]

0 голосов
/ 18 января 2017

очень милое доказательство концепции, которое я сделал немного раньше (чтобы увидеть, имеет ли значение порядок наследования); дайте мне знать, если ваша реализация C ++ фактически отвергает ее (моя версия gcc дает только предупреждение о назначении анонимных структур, но это ошибка), мне любопытно.

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

/** 
 * 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;
}

выход:

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!!!!

примечание, так как я никогда не выделяю свой поддельный объект, нет необходимости делать какое-либо уничтожение; деструкторы автоматически помещаются в конец области динамически размещаемых объектов, чтобы восстановить память самого литерала объекта и указателя vtable.

0 голосов
/ 19 сентября 2008

Ответы Берли здесь верны, за исключением вопроса:

У абстрактных классов просто есть NULL для указателя функции хотя бы на одну запись?

Ответ заключается в том, что для абстрактных классов виртуальная таблица вообще не создается. В этом нет необходимости, поскольку объекты этих классов не могут быть созданы!

Другими словами, если мы имеем:

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

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

Указатель vtbl, доступ к которому осуществляется через pB, будет vtbl класса D. Именно так реализуется полиморфизм. То есть, как к методам D обращаются через pB. Нет необходимости в vtbl для класса B.

В ответ на комментарий Майка ниже ...

Если класс B в моем описании имеет виртуальный метод foo () , который не переопределяется D, и виртуальный метод bar () , который переопределяется, то v'stb D будет иметь указатель на B foo () и собственный bar () . До сих пор нет vtbl, созданного для B.

...