4

Рассмотрим следующий код:Вызов функции сестра класса C++

#include <iostream> 

class A 
{ 
public: 
    virtual void f() = 0; 
    virtual void g() = 0; 
}; 

class B : virtual public A 
{ 
public: 
    virtual void f() 
    { 
     g(); 
    } 
}; 

class C : virtual public A 
{ 
public: 
    virtual void g() 
    { 
     std::cout << "C::g" << std::endl; 
    } 
}; 

class D : public C, public B 
{ 
}; 

int main() 
{ 
    B* b = new D; 
    b->f(); 
} 

Выход следующей программы C::g.

Как компилятор вызывает функцию дочернего класса класса B ??

+0

Не делайте этого, это так называемый бриллиант смерти, см. Https://en.wikipedia.org/wiki/Multiple_inheritance – Bernhard

+0

Я знаю, что это такое, я хочу знать, как это работает. –

+4

Это может помочь, если вы объясните, почему вы думаете, что это не сработает. Не могли бы вы также смутить, если бы вы изменили первую строку 'main' на' A * b = new D; '? – Hurkyl

ответ

6

N3337 10,3/9

[Примечание: интерпретация вызова виртуальной функции зависит от типа объекта, для которого он является называется (динамический тип), тогда как интерпретация вызова не виртуальной функции-члена зависит только от типа указателя или ссылки, обозначающей этот объект (статический тип) (5.2.2).- конец примечание]

Динамический тип является типом, к которому указатель действительно точки, а не тип, который был объявлен , как заостренный типа.

Поэтому:

D d; 
d.g(); //this results in C::g as expected 

такой же, как:

B* b = new D; 
b->g(); 

И потому, что внутри вашей B::f вызов g() является (неявно) призвал this указатель которого динамический тип является D, вызов разрешается до D::f, то есть C::f.

Если вы внимательно посмотрите, это (точно) такое же поведение, как показано в коде выше, только что b теперь неявноеthis вместо этого.

В этом весь смысл виртуальных функций.

2

Это поведение virtual: B вызов g через f но g будет решить во время выполнения (например, f). Таким образом, во время выполнения, единственным доступным переопределение g для D является один реализован в C

1

g разрешен во время выполнения, как и все виртуальные функции. Из-за способа D определено, что оно разрешено во все, что C реализует.

Если вы не хотите это поведения, вы должны либо позвонить в невиртуальные реализации g (вы можете делегировать эту функцию из виртуальной, а), или явно вызывать реализацию B «s с помощью B::g().

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

0

Виртуальные вызовы функций разрешаются во время выполнения, ссылаясь на номер VTable экземпляра. Для любого виртуального класса существует отдельный VTable (поэтому в каждом из A, B, C и D есть VTable). Каждый экземпляр среды выполнения имеет указатель на одну из этих таблиц, определяемую его динамическим типом.

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

На практике это означает, что ваш класс D имеет запись VTable для g, которая указывает на реализацию C. Даже при доступе через статический тип B он все равно будет ссылаться на этот же VTable для разрешения g.

+0

«Отдельный VTable существует для любого объявленного класса_« 1) только для «виртуальных» классов, а не для всех объявленных классов; 2) по крайней мере один vtable, а не один – curiousguy