Почему базовый указатель может получить доступ к производной переменной-члену в виртуальной функции - PullRequest
0 голосов
/ 27 апреля 2018
class Base {
public:
    virtual void test() {};
    virtual int get() {return 123;}
private:
    int bob = 0;
};

class Derived: public Base{
public:
    virtual void test() { alex++; }
    virtual int get() { return alex;}
private:
    int alex = 0;
};
Base* b = new Derived();
b->test();

Когда вызываются test и get, передается неявный указатель this. Это потому, что Derived классы имеют макет вспомогательной памяти, который идентичен тому, что был бы чистый базовый объект, тогда this Указатель работает как для базового указателя, так и для производного указателя?

Другой способ выразиться в том, что расположение памяти для Derived похоже на

vptr <-- this
bob
alex

Вот почему он может использовать alex в b->test(), верно?

Ответы [ 2 ]

0 голосов
/ 27 апреля 2018

Внутри методов Derived неявный указатель this всегда является указателем Derived* (в более общем смысле указатель this всегда соответствует вызываемому типу класса). Вот почему Derived::test() и Derived::get() могут получить доступ к члену Derived::alex. Это не имеет ничего общего с Base.

Структура памяти объекта Derived начинается с элементов данных Base, за которыми следует необязательное заполнение, после которого следуют элементы данных Derived. Это позволяет вам использовать объект Derived везде, где ожидается объект Base. Когда вы передаете указатель Derived* на указатель Base* или ссылку Derived& на ссылку Base&, компилятор будет корректировать указатель / ссылку соответствующим образом во время компиляции, чтобы указывать на часть Base Derived объект.

Когда вы вызываете b->test() во время выполнения, где b является указателем Base*, компилятор знает, что test() is virtual и сгенерирует код, который обращается к соответствующему слоту в vtable b и вызовите метод, на который указывает Но компилятор не знает, на какой тип производного объекта b на самом деле указывает время выполнения (в этом вся магия полиморфизма), поэтому он не может автоматически настроить неявный указатель this на правильный тип производного указателя. во время компиляции.

В случае, когда b указывает на объект Derived, vtable b указывает на vtable Derived. Компилятор знает точное смещение начала Derived от начала Base. Таким образом, слот для test() в vtable Derived будет указывать на частную заглушку, сгенерированную компилятором, чтобы настроить неявный указатель Base *this на указатель Derived *this, а затем перейти к фактическому коду реализации для * 1046. *.

За кулисами это примерно (не совсем) реализовано как следующий псевдокод:

void Derived_test_stub(Base *this)
{
    Derived *adjusted_this = reinterpret_cast<Derived*>(reinterpret_cast<uintptr_t>(this) + offset_from_Base_to_Derived);
    Derived::test(adjusted_this);
}

int Derived_get_stub(Base *this)
{
    Derived *adjusted_this = reinterpret_cast<Derived*>(reinterpret_cast<uintptr_t>(this) + offset_from_Base_to_Derived);
    return Derived::get(adjusted_this);
}

struct vtable_Base
{
    void* funcs[2] = {&Base::test, &Base::get};
};

struct vtable_Derived
{
    void* funcs[2] = {&Derived_test_stub, &Derived_get_stub};
};

Base::Base()
{
    this->vtable = &vtable_Base;
    bob = 0;
}

Derived::Derived() : Base()
{
    Base::vtable = &vtable_Derived;
    this->vtable = &vtable_Derived;
    alex = 0;
}

...

Base *b = new Derived;

//b->test(); // calls Derived::test()...
typedef void (*test_type)(Base*);
static_cast<test_type>(b->vtable[0])(b); // calls Derived_test_stub()...

//int i = b->get(); // calls Derived::get()...
typedef int (*get_type)(Base*);
int i = static_cast<get_type>(b->vtable[1])(b); // calls Derived_get_stub()...

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

0 голосов
/ 27 апреля 2018

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...