2012-02-02 3 views
8
class base { 
public: 
    void virtual fn(int i) { 
     cout << "base" << endl; 
    } 
}; 

class der : public base{ 
    public: 
    void fn(char i) { 
     cout << "der" << endl; 
    } 
}; 

int main() { 

    base* p = new der; 
    char i = 5; 
    p->fn(i); 
    cout << sizeof(base); 
    return 0; 
} 

Здесь подпись функции fn, определенной в классе base, отличается от сигнатуры функции fn(), определенной в классе der, хотя имя функции такое же. Следовательно, функция, определенная в классах der, скрывает base функция класса fn(). Таким образом, класс der версии fn не может быть вызван вызовом p->fn(i); Это нормально.Почему vptr требуется, когда производный класс не переопределяет виртуальную функцию?

Моя точка, то почему sizeof класс base или der является 4 если нет использования указателя виртуальных таблиц? Что требуется для VTABLE указатель здесь?

+0

Вы когда-нибудь слышали о перегрузке на C++? –

+0

@KamilKlimek: Перегрузка - это акт объявления нескольких функций с различными сигнатурами. Вероятно, вы имеете в виду переопределение (это переопределение методов в производных классах). –

+0

для вашей любезной информации, это не перегруженная функция. – user966379

ответ

6

Обратите внимание, что это зависит от реализации. & может отличаться для каждого компилятора.

Требование наличия vtable состоит в том, что базовый класс предназначен для наследования и расширения, а его производный класс может переопределить метод.

Два класса Base и Derived могут находиться в разных единицах перевода, а компилятор при компиляции базового класса не будет знать, будет ли метод переопределен или нет. Итак, если он находит ключевое слово virtual, он генерирует vtable.

+1

Но в этом примере весь код * есть * в том же модуле, и компилятор мог это знать. –

0

Наследование является отношением is-a. der есть-base. base имеет размер 4, der будет иметь как минимум размер 4. vftableptr является членом base, он будет членом der.

base имеет виртуальный метод, поэтому он будет иметь указатель на виртуальную таблицу, независимо от того, используете вы ее или нет.

+0

Вам не хватает точки, запрос Q OP - это * почему размер базового класса '4'? * И не * Почему размер производного класса равен' 4'? * –

+0

Если ни один из производных классов никогда не переопределяет virtual fucntn –

+0

@Als Я был смущен, потому что это кажется мне довольно простым. У этого есть виртуальный метод, почему он не должен иметь указатель на vtable? Я отредактировал свой ответ, чтобы отразить это. –

1

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

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

Очевидно, автор компилятора не счел нужным делать это. Вероятно, потому, что он не будет использоваться очень часто, и потому, что вы можете сделать это самостоятельно, удалив virtual из базового класса.

+0

Виртуальный b (int), B: public A - no override, C: public B override b (int), D: public C - no override. И как вы, кроме компилятора, должны вести себя в этом случае? Где должно быть vtable, а где нет? если I B * instance = new D; ? –

+0

Также, если он используется как фабрика плагинов? Он не имеет физического способа узнать, будет ли или не будет унаследованный класс, который переопределяет виртуальный метод. –

+0

Если в любом месте есть переопределение, виртуальная таблица должна быть там повсюду, потому что может быть только одна версия каждого класса. Это просто, если (большой, если!) Компилятор может точно сказать, что он не нужен, его можно оптимизировать. –

1

Компилятор не может оптимизировать из vtable переменной-члена из класса «базовой», потому что там может быть другой исходный файл в той же или другой проект, который будет содержать следующее:

struct ived : base { 
    ived() : p(new char[BIG_DATA_SIZE]) {} 
    virtual ~ived(); 
    virtual void fn(int); 
private: 
    char* p; 
}; 

деструктор и fn мог быть реализованы в другом месте:

ived::~ived() { delete[] p; } 

void ived::fn(int) { 
    cout << "ived" << endl; 
} 

И где-то в другом месте может быть такой код:

base* object = new ived; 
ived->fn(0); 
delete object; 
cout << sizeof(base) << endl; 

Итак, возникли бы две проблемы: виртуальная функция ived::fn не вызывалась, виртуальный деструктор не вызывался, поэтому BIG_DATA_SIZE не удалялся. В противном случае, sizeof(base) здесь были бы разными. Вот почему компиляторы всегда генерируют vtable для любого класса с виртуальной функцией-членом или виртуальным базовым классом.

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