Виртуальная функция C++, не вызванная в подклассе

Рассмотрите эту простую ситуацию:

A.h

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

B.h

#include <iostream>

class B {
public:
    virtual void b() {std::cout << "b()." << std::endl;};
};

C.h

#include "A.h"
#include "B.h"

class C : public B, public A {
public:
    void a() {std::cout << "a() in C." << std::endl;};
};

int main() {
    B* b = new C();
    ((A*) b)->a();    // Output: b().

    A* a = new C();
    a->a();           // Output:: a() in C.

   return 0;
}

Другими словами:
- A является чистым виртуальным классом.
- B является классом без суперкласса и одной нечистой виртуальной функции.
- C является подклассом A и B и переопределяет A чистая виртуальная функция.

То, что удивляет меня, является первым выводом т.е.

((A*) b)->a();    // Output: b().

Хотя я звоню () в коде, b () вызывается. Мое предположение - то, что это связано с тем, что переменная b является указателем на класс B, который не является подклассом класса A. Но тем не менее тип выполнения является указателем на экземпляр C.

Что точное правило C++ состоит в том, чтобы объяснить это, с точки зрения Java, странного поведения?

7
задан Marc 20 January 2010 в 21:52
поделиться

8 ответов

Вы безоговорочно отличаются b к A * , использующему CAST CHENT . Компилятор не останавливает вас от этого; Вы сказали, что это A * , так что это A * . Таким образом, он обрабатывает память, которую он указывает на понравившийся экземпляр A . С A () является первым методом, перечисленным в A VTable и B () - первый метод, перечисленный в b S VTable, когда вы звоните A () на объекте, который на самом деле B , вы получаете B () .

Тебе повезло, что планировка объекта похожа. Это не гарантируется дело.

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

Во-вторых, вы не должны полагаться на такое поведение, если только вы не используете Dynamic_Cast <> .

24
ответ дан 6 December 2019 в 04:54
поделиться

Не используйте элемент C-стиля при отливках по нескольким наследственным дереву. Если вы используете Dynamic_cast вместо этого вы получаете ожидаемый результат:

B* b = new C();
dynamic_cast<A*>(b)->a();
11
ответ дан 6 December 2019 в 04:54
поделиться

Вы начинаете с B * и бросить его к *. Поскольку двое не связаны, вы углубляетесь в сферу неопределенного поведения.

5
ответ дан 6 December 2019 в 04:54
поделиться

((a *) b) - это явный элемент C-стиль, который допускается независимо от того, какие типы указаны. Однако, если вы попытаетесь разымевать этот указатель, это будет либо ошибка времени выполнения, либо непредсказуемое поведение. Это пример последнего. Выход, который вы наблюдали, ни в коем случае не является безопасным или гарантированным.

2
ответ дан 6 December 2019 в 04:54
поделиться

Следующая строка представляет собой ReinterPret_cast, какие точки в той же памяти, но «притворяются на вид, это другой вид объекта:

((A*) b)->a();

, что вы действительно хотите, это Dynamic_cast, что проверяет, какой объект B действительно и настроить Какое местоположение в памяти, чтобы указать на:

dynamic_cast<A*>(b)->a()

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

1
ответ дан 6 December 2019 в 04:54
поделиться

В C ++ почти никогда не случается, где использование литого типа C C ++ ReinterPret_cast <>) оправдан или требуется. Всякий раз, когда вы находите соблазн использовать один из двух, подозреваю ваш код и / или ваш дизайн.

1
ответ дан 6 December 2019 в 04:54
поделиться

Я думаю, у вас есть тонкий ошибок в литье из B * - A * , а поведение не определено. Избегайте использования Chasts C-образных кассов и предпочитаю карьеры C ++ - в этом случае Dynamic_Cast . Из-за того, как ваш компилятор выложил хранилище для типов данных и записи VTable, вы получили нахождение адреса другой функции.

0
ответ дан 6 December 2019 в 04:54
поделиться

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

Поскольку A и B являются двумя разными основаниями C, то, что вы пытаетесь здесь сделать, называется кросс-дастингом . Единственное, что может выполнять кросс-астинг в языке Си++ - это dynamic_cast. Это то, что вы должны использовать в данном случае в случае, если вам это действительно нужно (не так ли?)

B* b = new C(); 
A* a = dynamic_cast<A*>(b);
assert(a != NULL);
a->a();    
2
ответ дан 6 December 2019 в 04:54
поделиться
Другие вопросы по тегам:

Похожие вопросы: