2016-07-10 27 views
6

Znalazłem kilka innych pytań, które wydają się sugerować moje pytanie, ale żadna bezpośrednio nie odpowiedziała na to (nawet the one o prawie tej samej nazwie).Dlaczego upcasting wskaźnika podrzędnego do klasy bazowej czasami zmienia wartość wskaźnika?

Mam klasę potomną C++ wywodzącą się od dwóch rodziców. Tworzę obiekt tej klasy, a następnie umieszczam jego odniesienie do każdego z dwóch rodziców. Wartości rzutowania nie są takie same, chociaż jedna pozostaje wartością odniesienia podrzędnego. Oto kod:

class Mom 
{ 
public: 
    int a = 1; 
}; 

class Dad 
{ 
public: 
    int b = 2; 
}; 

class Child : public Mom, public Dad 
{ 
public: 
    int c = 3; 
}; 

int main() 
{ 
    Child* c = new Child; // 0x00C5A618 

    Mom* m = c; // 0x00C5A618 
    Dad* d = c; // 0x00C5A61C 

    return 0; 
} 

Teraz mam zgadywać, że adresy uzyskano poprzez zastosowanie static_cast <> są te rzeczywistych podobiektów. Tak się składa, że ​​Mom jest pierwszym obiektem podrzędnym, więc w rzeczywistości ma ten sam adres, co Child. Dad to drugi podobiekt, więc znajduje się pod innym adresem.

Jak rozumiem, C-styl rzuca, jak to

Mom* m = (Mom*)c; 
Dad* d = (Dad*)c; 

będzie stosować static_cast <> kiedy byłoby legalne, aby to zrobić. Rzeczywiście, kiedy faktycznie skompilowałem i wykonałem tę wersję, widziałem to samo zachowanie, co poprzednio.

Kontynuując tę ​​migające sagę, używając ukrytych odlewane, jak to

Mom* m = c; 
Dad* d = c; 

wykazują takie samo zachowanie, jak również, z którego wywnioskować, że ukryte odlewy zastosować również static_cast <> gdy ekwiwalent jawne oddanych byłoby legalne.

rzutowanie w dół z powrotem do Child robi to samo:

Child* k = static_cast<Child*>(d); // 0x00C5A618 

Wartość w k jest różna od wartości w d, które zostały ustawione z powrotem do wartości pierwotnie w c.

Jestem zgadywania, że ​​w tym przypadku nie jest sprawdzenie run-czas, aby zobaczyć, czy rzeczywiście d punktów do Dad podobiektu z Child obiektu.

Do tej pory wszystko ma sens, ale jestem zaskoczony, że nie ma takiego dostosowania adresu (jeśli jest to słuszne to nazwać) dla hierarchii pojedynczo wyprowadzonych. Oznacza to, że jeśli moja hierarchia jest po prostu Mom ->Dad ->Child, rzucając odniesienie do Child do wskaźnika do albo Mom albo nigdy Dad zmienia wartość:

class Mom 
{ 
public: 
    int a = 1; 
}; 

class Dad : public Mom 
{ 
public: 
    int b = 2; 
}; 

class Child : public Dad 
{ 
public: 
    int c = 3; 
}; 

int main() 
{ 
    Child* c = new Child; // 0x00C5A618 

    Mom* m = c; // 0x00C5A618 
    Dad* d = c; // 0x00C5A618 

    Child* k = static_cast<Child*>(d); // 0x00C5A618 

    return 0; 
} 

bym tego spodziewać aby zmienić rozmiar pamięci potrzebnej na każdą dodatkową podklasę lub cztery bajty (rozmiar int). Ale nie robią tego. Wszystkie wskaźniki w powyższym przykładzie, c, m, d i k, mają tę samą wartość.

Więc teraz mam zgadywać, że obiekty zostały rozplanowane w pamięci z obiektem Mom w najniższym adresu, dodatkową przestrzeni potrzebnej dla obiektu Dad następnego w kolejności, a dodatkową przestrzeni potrzebnej do Child nadchodzi ostatni. Ponieważ adres Dad to-Mom, adres byłby taki sam jak adres Mom, prawda?

Podobnie jest również z Child, więc jego adres będzie również taki sam jak jego własny adres podobiektu Mom.

Więc ...

myślę, że to działa tak: w pamięci, obiekty następują po sobie tak, że w czysto pojedynczo pochodzącego hierarchii upcasting i rzutowanie w dół zawsze daje ten sam adres, podczas gdy w mnożyć Hierarchia wyprowadzona, niektóre upcasty dają inny adres, ponieważ nie wszystkie podobiekty będą uruchamiane pod tym samym adresem, co obiekt pochodny. Tak:

Possible Memory Layout for a Singly Derived Hierarchy 

+-------+-----+-----+---+ 
| Child : Dad : Mom : a | 
|  |  +-----+---+ 
|  |   : b | 
|  +-----------+---+ 
|     : c | 
+-----------------------+ 


Possible Memory Layout for a Multiply Derived Hierarchy 

+-------+-----+---+ 
| Child : Mom : a | 
|  +-----+---+ 
|  : Dad : b | 
|  +-----+---+ 
|    : c | 
+-----------------+ 

Przepraszam, że mam wałęsał się trochę, ale moje pytanie jest takie: czy fakt, że uskok do niektórych klas bazowych obiektu pomnożyć pochodzącego zmienia wartość odniesienia wyniku fakt, że wartość po odlaniu jest adresem obiektu podstawowego w pamięci?

+1

Tak ........... – Soren

Odpowiedz

3

Tak, masz rację. Upcasting the indicator podaje adres podrzędnego obiektu macierzystego, który jest przechowywany gdzieś w klasie potomnej. Zazwyczaj obiekt podrzędny nadrzędny jest przechowywany na początku obiektu podrzędnego, więc wskaźniki do obiektu podrzędnego podrzędnego i podstawowego są takie same.

W przypadku wielokrotnego dziedziczenia, nie jest możliwe, aby wszyscy rodzice zajęli tę samą pamięć (zignorujmy możliwą Pustą Optymalizację Bazy), więc wszystkie, ale jedno, adresy klasy bazowej będą musiały różnić się od adresu podrzędnego.

2

Jestem zgadywania, że ​​w tym przypadku nie jest sprawdzenie run-czas, aby zobaczyć, czy rzeczywiście d wskazuje na Dad podobiektu z Child obiektu.

Nie, jeśli chcesz sprawdzić środowisko wykonawcze, użyj dynamic_cast (ale typ źródła musi być polimorficzny). Przy static_cast kompilator po prostu przyjmie typ jest poprawny, w przeciwnym razie wystąpi niezdefiniowane zachowanie.

myślę, że to działa tak: w pamięci, obiekty następują po sobie tak, że w czysto pojedynczo pochodzącego hierarchii upcasting i rzutowanie w dół zawsze daje ten sam adres

to nie jest gwarantowane; standard pozostawia to nieokreślone. Kompilator nie musi rozkładać podobiektów klasy podstawowej i elementów, tak aby podobiekty klasy bazowej były na pierwszym miejscu, chociaż wydaje się, że twój kompilator robi w takich przypadkach; ale mogą istnieć inne przypadki, w których adres może być inny, na przykład jeśli klasa pochodna zawiera funkcje wirtualne, a klasa podstawowa nie. (W takim przypadku układ może zostać vptr, następnie klasy bazowej podobiekt, następnie podobiekty użytkownika.)

jest fakt, że uskok niektórych klas podstawy przedmiotu wielokrotnie pochodzącego zmienia wartość odniesienia wynika z faktu, że wartość po odlaniu jest adresem obiektu podstawowego w pamięci?

Tak.

+0

To trochę głupie w głowie! Jako stary programista C pomysł, że rzutowanie wskaźnika przyniesie inną wartość niż sam wskaźnik, jest trudny do zaakceptowania. Kolejny dowód, że nie można po prostu założyć, że C-podobny kod w C++ zachowuje się tak, jak w prawdziwym C. –