Вызов функции производного класса из указателя базового класса после его приведения к указателю производного класса - PullRequest
0 голосов
/ 06 января 2020

Я довольно новичок в C ++ (& OOP). Я пытаюсь понять следующий фрагмент кода:

#include <iostream>

class Base {
public:
    Base() {
        std::cout << "In Base Constr: " << __FUNCSIG__ << std::endl;
    }

    virtual ~Base() {
        std::cout << "In Base Destr: " << __FUNCSIG__ << std::endl;
    }

    void A() {
        std::cout << "In Base func A " << __FUNCSIG__ << std::endl;
    }
};

class Derived : public Base {
    public:
    Derived() {
        std::cout << "In Derived Constr: " << __FUNCSIG__ << std::endl;
    }

    ~Derived() {
        std::cout << "In Derived Destr: " << __FUNCSIG__ << std::endl;
    }

    void B() {
        std::cout << "In Derived func B " << __FUNCSIG__ << std::endl;
    }
};

void test(Base* b) {
    Derived* d = static_cast<Derived*>(b);
    d->A();
    d->B();              // How is this valid??
}

int main() {
    Base *b = new Derived();
    std::cout << "In main" << std::endl;
    b->A();
    std::cout << __LINE__ << std::endl;

    Base *bb = new Base();
    std::cout << __LINE__ << std::endl;
    test(bb);

    delete b;
    delete bb;
}

Я не уверен, почему и как работает строка d->B()? хотя указатель был приведен к типу Derived, но сам объект базового класса не должен иметь эту функцию в памяти.

Ответы [ 3 ]

3 голосов
/ 06 января 2020

Я не уверен, почему и как работает строка b-> B ()? [..] Сам объект базового класса не должен иметь эту функцию в памяти

Вы правы! Это не работает!

(ну, функции не хранятся "в памяти", но ...)

Вызов недействителен. static_cast говорит: «Я обещаю, что это Base* указывает на Derived». Это обещание было нарушено.

Программа имеет неопределенное поведение. На практике это может означать, что все «кажется» работает, особенно если никакие переменные-члены не были затронуты несуществующей функцией ...

2 голосов
/ 06 января 2020

Не определено поведение static_cast для производного класса, когда объект на самом деле не относится к этому производному типу. Но неопределенное поведение означает, что все может случиться, в том числе и работать. (Или, кажется, работает сегодня, а затем терпит неудачу позже в самое неподходящее время.)

Так что это все, что нужно для официального объяснения с точки зрения языка C ++.

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

void __mangled_Derived_B(Derived const* this) { /*...*/ }

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

В вашем примере Derived::B на самом деле вообще не использует this, даже неявно, поэтому проблемы маловероятны. Но если бы он попытался использовать элемент данных Derived, все стало бы намного рискованнее, что могло привести к странным результатам, изменениям в других объектах или сбоям.

1 голос
/ 06 января 2020

Недействительно. Это неопределенное поведение.

Дело в том, что компилятор позволяет вам писать такой код. Преобразование из Base* в Derived* будет допустимым, если объект, на который указывает объект, фактически является Derived. Программист должен убедиться, что он действителен.

В C ++ есть много ситуаций, когда вы можете выстрелить себе в ногу вот так. Это часть языка.

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

...