2010-01-20 3 views
7

Рассмотрим простую ситуацию: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; 
} 

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

Что меня удивляет это первый выход, т.е.

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

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

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

+2

Проще говоря: ** B не являются A-х ** Они совершенно не связаны друг с другом, но ваш (нехорошо использовать!) Стилирование в стиле C все равно. 'dynamic_cast' будет правильно перемещать вашу иерархию. Когда вы применяете несвязанные типы указателей, вы получаете неопределенное поведение. Это означает, что все может случиться, из-за этого, похоже, работает, чтобы выдуть ваш компьютер. – GManNickG

+0

Не забывайте носовых демонов. –

ответ

24

Вы безоговорочно литье b на A* с использованием Литой C-стиль. Компилятор не мешает вам это делать; вы сказали, что это A*, так что это A*. Поэтому он обрабатывает память, на которую он указывает, например, экземпляр A. Поскольку a() является первым методом, указанным в таблице v12 таблицы A, и b() является первым методом, указанным в таблице v, когда вы вызываете a() на объект, который действительно является B, вы получаете b().

Вам повезло, что макет объекта аналогичен. Это не гарантируется.

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

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

+0

Вы можете полностью полагаться на кросс-кастинг, если используется 'dynamic_cast'. На 100% гарантируется работа или деньги. – coppro

+0

@coppro: если вы используете dynamic_cast, да. Я пытался подчеркнуть, что не полагаюсь на это, работая с актом C-стиля. Я обновил последнее заявление, чтобы сделать это более понятным. –

+0

+1 за то, что вам повезло. –

11

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

B* b = new C(); 
dynamic_cast<A*>(b)->a(); 
+0

Это должно быть ошибка времени выполнения в приведенном примере. Верный? –

+0

@tehMick, no это будет неопределенное поведение – Trent

+0

@teh @Trent: Вы ошибаетесь. : P Правильно работает, перемещая иерархию наследования. – GManNickG

2

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

5

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

0

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

1

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

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

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

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

Как jeffamaphone упоминалось, подобное расположение двух классов, что вызывает неправильную функцию, которая будет вызвана.

1

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

2

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

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

B* b = new C(); 
A* a = dynamic_cast<A*>(b); 
assert(a != NULL); 
a->a();  
Смежные вопросы