Приведение между двумя родителями мульти наследуемого класса с виртуальными функциями приводит к странному поведению - PullRequest
0 голосов
/ 23 марта 2020

Ниже приведен код. Я не понимаю, почему он ведет себя так:

#include <iostream>
using namespace std;

class FooInterface {
public:
    virtual ~FooInterface() = default;
    virtual void Foo() = 0;
};

class BarInterface {
public:
    virtual ~BarInterface() = default;

    virtual void Bar() = 0;
};

class Concrete : public FooInterface, public BarInterface {
public:
    void Foo() override { cout << "Foo()" << endl; }
    void Bar() override { cout << "Bar()" << endl; }
};

int main() {
    Concrete c;
    c.Foo();
    c.Bar();

    FooInterface* foo = &c;
    foo->Foo();

    BarInterface* bar = (BarInterface*)(foo);
    bar->Bar(); // Prints "Foo()" - WTF?
}

Последнее утверждение bar-> Bar () печатает "Foo ()", что меня смутило. Это из следующего блога: https://shaharmike.com/cpp/vtable-part4/. В основном это относится к структуре виртуальной таблицы класса и к тому, как компилятор обрабатывает приведение между двумя родительскими классами мульти-унаследованного класса с виртуальными функциями. Кто-нибудь может помочь мне понять это?

1 Ответ

2 голосов
/ 23 марта 2020

Когда вы пишете (BarInterface*)(foo);, вы лжете компилятору. Вы говорите, что foo действительно указатель на BarInterface, поэтому компилятор вам поверит. Поскольку это не так, вы получаете неопределенное поведение, когда пытаетесь разыменовать указатель. Обсуждать, как ведет себя скомпилированная программа в присутствии неопределенного поведения, часто бессмысленно.

В этом случае способ, которым ваш компилятор заполнил vtables, запись для FooInterface::Foo, кажется, находится в том же месте, что и запись для BarInterface::Bar. В результате, когда вы вызываете bar->Bar(), компилятор просматривает таблицу FooInterface, находит запись для FooInterface::Foo и вызывает ее. Если расположение классов будет другим или сигнатуры функций будут другими, это приведет к гораздо более серьезным последствиям.

Разумеется, решение состоит в использовании dynamic_cast, который может выполнять необходимое боковое приведение.

...