2016-04-06 5 views
14

Мне был задан этот вопрос с интервью, и я понял, что это неправильно. «What is the output»: Мой ответ был 135, фактический результат равен 136. Это означает, что указатель на два родительских класса не равен, даже если они проходят предыдущий тест, равный дочернему классу. Я думал, что понял C++ указатели, но это меня озадачило для объяснения. Хотя я думаю, что вижу, что происходит, я не знаю, почему. Любые эксперты C++, которые могут предложить техническое объяснение? Оказывается, первые два сравнения являются более логичными в природе, в то время как последнее сравнение более буквальное ...понимание указателей C++ для родительских/базовых классов

#include <iostream> 

class A 
{ 
    public: 
    A() : m_i(0) { } 

    protected: 
    int m_i; 
}; 

class B 
{ 
    public: 
    B() : m_d(0.0) { } 

    protected: 
    double m_d; 
}; 

class C 
    : public A, public B 
{ 
    public: 
    C() : m_c('a') { } 

    private: 
    char m_c; 
}; 

int main() 
{ 
    C c; 
    A *pa = &c; 
    B *pb = &c; 

    const int x = (pa == &c) ? 1 : 2; 
    const int y = (pb == &c) ? 3 : 4; 
    const int z = (reinterpret_cast<char*>(pa) == reinterpret_cast<char*>(pb)) ? 5 : 6; 

    std::cout << x << y << z << std::endl; 
    return 0; 
} 
+1

@SethKitchen Я думаю, вы имеете в виду, что у них одинаковый динамический тип, но разные статические типы? 'pa' - это' A' (статический), указывающий на 'C' (динамический), тогда как' pb' - это 'B' (статический), указывающий на' C' (динамический) – Kevin

+0

. Вы можете подумать о добавлении языка -lawyer', чтобы получить стандартное объяснение, почему это происходит. – kfsone

+0

Мораль истории: не используйте множественное наследование ни для чего, кроме микшинов. – o11c

ответ

8

Может быть, если вы распечатать pa и pb это будет более понятно, что происходит.

std::cout << "A: " << pa << std::endl; 
std::cout << "B: " << pb << std::endl; 
std::cout << "C: " << &c << std::endl; 

Запуск этого в конце main дает мне

A: 0xbfef54e0 
B: 0xbfef54e4 
C: 0xbfef54e0 

(ваш вывод может быть разным, главное, что они не все равны)

Это происходит из-за как объект C представлен в памяти. Поскольку C является как A, так и B, он должен иметь элементы данных из каждой части. Реальное расположение C что-то вроде этого (не обращая внимания кожухов):

int A::m_i; 
double B::m_d; 
char C::m_c; 

При преобразовании C* к A*, компилятор знает, что A части начинается со смещением 0, поэтому значение указателя не меняется , Для C* - B* его необходимо компенсировать sizeof(int) (плюс прокладка). Эта обработка смещения выполняется автоматически для вас для расчета x и y. Для z он обходит с тех пор, как вы используете reinterpret_cast

+0

На самом деле я все это сделал. Почему я запутался в abpout, почему оператор: const int y = (pb == & c)? 3: 4; оценивается как true, что, как вы только что указали, технически не соответствует действительности. Указатель на «b» явно не равен указателю на «c». если вы запустите эту программу, вы увидите, что результат равен 136. – Gio

+1

взгляните на определение 'B * pb = & c;' Теперь вы видите это? Если вы не смотрите более внимательно. Если вы не читаете. 'y = (pb == & c)? 3: 4'. pb == & c истинно, потому что именно так оно и было определено, вы присвоили адрес '& c'' 'bd'. Позже, сравнение bool сделается слишком проверенным, если 'pb == & c'. Конечно, да! Надеюсь, что это прояснилось. –

+1

http://ideone.com/Vxeju4 Обратите внимание, что pb отличается от & c. Но это нормально, потому что & c является адресом c * как 'C *' *. Когда вы сравниваете 'pb == & c', вы не можете перетащить' pb' в 'C', но вы можете сбрасывать' C' на 'B', и когда вы выполняете сравнение верно - это дает тот же результат, что и задание, поэтому сравнение дает истину. – kfsone

2

C++ обычно описывает структуры классов один за другим. Позволяет проверить следующий пример кода:

#include <iostream> 
#include <stdio.h> 

//#define DO_PACKSTRUCTURES 
//#define DEFINE_VFUNC 

#ifdef DO_PACKSTRUCTURES 
#pragma pack(push, 1) 
#endif 


class A 
{ 
public: 
    A() : m_i(0) { } 

#ifdef DEFINE_VFUNC 
    virtual 
#endif  
    void myfunc() 
    { 
    } 

protected: 
    int m_i; 
}; 

class B 
{ 
public: 
    B() : m_d(0.0) { } 

#ifdef DEFINE_VFUNC 
    virtual 
#endif  
    void myfunc2() 
    { 
    } 

protected: 
    double m_d; 
}; 

class C 
    : public A, public B 
{ 
public: 
    C() : m_c('a') { } 

#ifdef DEFINE_VFUNC 
    virtual 
#endif  
    void myfunc3() 
    { 
    } 

    char m_c; 
}; 

#ifdef DO_PACKSTRUCTURES 
#pragma pack(pop) 
#endif 

void pprint(char* prefix, void* p) 
{ 
    printf("%s = %p\r\n", prefix, p); 
} 

int main() 
{ 
    C c; 
    A *pa = &c; 
    B *pb = &c; 

    pprint("&c", &c); 
    pprint("pa", pa); 
    printf("\r\n"); 

    pprint("pb", pb); 
    pprint("pa + sizeof(A)", ((char*)pa) + sizeof(A)); 
    printf("\r\n"); 

    pprint("&c.m_c", &c.m_c); 
    pprint("pb + sizeof(B)", ((char*)pb) + sizeof(B)); 
    printf("\r\n"); 
    printf("sizeof(A)=%d\r\n", sizeof(A)); 
    printf("sizeof(B)=%d\r\n", sizeof(B)); 
    printf("sizeof(C)=%d\r\n", sizeof(C)); 
    printf("sizeof(int)=%d\r\n", sizeof(int)); 
    printf("sizeof(double)=%d\r\n", sizeof(double)); 
    printf("sizeof(char)=%d\r\n", sizeof(char)); 
    printf("sizeof(void*)=%d\r\n", sizeof(void*)); 

    pa->myfunc(); 
    c.myfunc2(); 
    c.myfunc3(); 

    return 0; 
} 

Win32/Debug или Release/DO_PACKSTRUCTURES & DEFINE_VFUNC неопределенны:

&c = 00BBF7A4 
pa = 00BBF7A4 

pb = 00BBF7AC 
pa + sizeof(A) = 00BBF7A8 

& c.m_c = 00BBF7B4 
pb + sizeof(B) = 00BBF7B4 

sizeof(A) = 4 
sizeof(B) = 8 
sizeof(C) = 24 
sizeof(int) = 4 
sizeof(double) = 8 
sizeof(char) = 1 
sizeof(void*) = 4 

Так указатель на C * тот же указатель, как A * - потому, что А является первой базой класс, из которого начинается макет памяти. Так расположение памяти выглядит как-то так:

C*: 
    A 
    B 
    C members (m_c) 

пб не равна (ра + SizeOf (A)) - потому что компилятор добавить некоторые байты выравнивания между А и В, чтобы ускорить доступ к B. Не уверен, насколько важно эти оптимизации - создание миллионов экземпляров одного и того же класса может повлиять на производительность.

Win32/Debug или Release/DO_PACKSTRUCTURES определяется & DEFINE_VFUNC не определено:

&c = 00B9F770 
pa = 00B9F770 

pb = 00B9F774 
pa + sizeof(A) = 00B9F774 

&c.m_c = 00B9F77C 
pb + sizeof(B) = 00B9F77C 

sizeof(A)=4 
sizeof(B)=8 
sizeof(C)=13 
sizeof(int)=4 
sizeof(double)=8 
sizeof(char)=1 
sizeof(void*)=4 

Теперь мы не добавляя никаких выравнивания или байты заполнения (Из #pragma пакета (нажимной, 1)) - мы получили Класс C меньше, а также теперь pb == (pa + sizeof (A)).Теперь мы также видим, что такое «распределение» класса C, который является sizeof (int)/sizeof (double) + sizeof (char) = 4 + 8 + 1 = 13.

Win32/Debug или Release/DO_PACKSTRUCTURES & DEFINE_VFUNC определены:

&c = 007EFCF4 
pa = 007EFCF4 

pb = 007EFCFC 
pa + sizeof(A) = 007EFCFC 

&c.m_c = 007EFD08 
pb + sizeof(B) = 007EFD08 

sizeof(A)=8 
sizeof(B)=12 
sizeof(C)=21 
sizeof(int)=4 
sizeof(double)=8 
sizeof(char)=1 
sizeof(void*)=4 

у нас еще есть матч расчета указатель, как и в предыдущем случае, но если рассчитать размер с помощью SizeOf (класс) - мы получим правильный размер, но не SizeOf (тип элемента) - из-за «виртуальной 'keyword - фактически ключевое слово virtual выделяет дополнительный размер указателя виртуальной таблицы.

It reflects 21 - 8 - 4 - 1 = 8 
8/sizeof(void*) = 2 - that's two vtables - one from A and another from B class instances. 

Я не уверен, почему сам C класс не имеет свои собственные виртуальные таблиц - для моего лучшего понимания он должен. Это можно понять позже. Компилятор Microsoft Visual C++ также имеет специальный вид ключевого слова __declspec (novtable), который также имеет некоторое представление о том, как генерируются vtable. Но что-то нормальным разработчикам не нужно, если только вы не имеете дело с расширенным программированием COM.

Смежные вопросы