2

Я действительно смущен. Я побежал в следующей ситуации, где С наследуется от А и В, но в зависимости от того, как вещи назначены, я получаю другое поведение:Доступ к члену производного класса из указателя базового класса

  • Если бы я new в C экземпляр, и хранить его в A указатель , а затем присвоить это значение к B указателя, и вызов метода A с помощью A указателя, и способ B с B указателем, я получаю странные вещи ... (см Test 1).

  • Если я new в C экземпляр, и хранить его в C указатель, а затем присвоить это значение указателя A и B, и вызов метода A с помощью A указателя, и способ B с B указатель, я получаю то, что ожидаю ... (см. Тест 2).

Мой вопрос: Почему тест 1 ведет себя так, как он делает?

Класс A

class A 
{ 
public: 
    A() { aMember = 'A'; } 
    virtual ~A() {} 
    virtual char getAMember() { return aMember; } 
private: 
    char aMember; 
}; 

Класс B

class B 
{ 
public: 
    B() { bMember = 'B'; } 
    virtual ~B() {} 
    virtual char getBMember() { return bMember; } 
private: 
    char bMember; 
}; 

Класс C

class C : public A, public B 
{ 
public: 
    C() : A(), B() {} 
    virtual ~C() {} 
}; 

Главное, что имеет Test1 & Test2

#include <cstdio> 

int main(void) 
{ 
    C* c; 
    A* a; 
    B* b; 

    printf("Test 1\n"); 

    a = new C(); 
    b = (B*)a; 

    printf("a->getAMember(): %c\n",a->getAMember()); // prints A 
    printf("b->getBMember(): %c\n",b->getBMember()); // prints A ?! 

    printf("Test 2\n"); 

    c = new C(); 
    a = c; 
    b = c; 

    printf("a->getAMember(): %c\n",a->getAMember()); // prints A 
    printf("b->getBMember(): %c\n",b->getBMember()); // prints B 

    return 0; 
} 
+0

Опасная вещь о приведении указателя заключается в том, что компилятор позволит вам это сделать, даже если это не имеет смысла. –

+0

Вы также можете прочитать следующее сообщение: http://stackoverflow.com/questions/9374244/casting-pointer-to-base-class-into-a-pointer-to-derived-class –

ответ

3

Вы используете злую бросание C-типа, который в данном случае эквивалентно reinterpret_cast. Это переинтерпретирует адрес под-объекта A, как и под-объект B, который фактически находится на другом адресе; приведение недействительно, что дает неопределенное поведение, если вы попытаетесь получить доступ к B через этот указатель.

Использовать dynamic_cast для безопасного перекрестного перевода между подпоследователями класса полиморфного базового класса с одним и тем же полным объектом (не забудьте проверить результат, если вы набрасываете указатель); или static_cast в производный класс (в данном случае - C*), если вы абсолютно уверены, что указываете на объект такого типа.

Во втором случае вы безопасно переходите от производного класса к базовым классам, поэтому все четко определено без необходимости кастинга.

1

Это потому, что этот литой b = (B*)a; вызывает неопределенное поведение. Кастинг, подобный этому, вызывает тип a. Вы должны использовать dynamic_cast.

a = new C(); 
b = dynamic_cast<B*>(a); 

Это позволит во время выполнения на самом деле проверить тип, из-за ваши виртуальные функции, а также производить правильный результат. Если бросок не является надлежащим броском, это приведет к существованию nullptr.