Rzutowanie w dół

0

Hej. Próbuję zrozumieć programowanie obiektowe. Mam taki przykład, dlaczego dostaję crasha? Przecież te wskaźniki cały czas wskazuję na to samo miejsce w pamięci (chyba).
Jak mam rozwiązać taki problem bez umieszczania funkcji wirtualnej z klasie bazowej? Mając jedną klasę bazową i 10 pochodnych z czego gdy każda ma np. po 15 metod robi się dodatkowe 150 metod wirtualnych w klasie bazowej co daje niezły syf. Jak takie coś rozwiązać?

#include <cstdio>

class KlasaBazowa {
	//virtual void Funkcja() = 0;
};

class KlasaPochodna : public KlasaBazowa {
public:
	virtual void Funkcja() { printf("Funkcja()\n"); }
};

int main() {
	KlasaPochodna* pochodna = new KlasaPochodna;
	pochodna->Funkcja();

	KlasaBazowa* bazowa = pochodna;

	KlasaPochodna* pochodna2 = reinterpret_cast<KlasaPochodna*>(bazowa);
	pochodna2->Funkcja();

	getchar();
	return 0;
}
1

Mam taki przykład, dlaczego dostaję crasha?

bo używasz reinterpret_cast zamiast static_cast albo C-style cast.

Przecież te wskaźniki cały czas wskazuję na to samo miejsce w pamięci (chyba).

Kiedy klasa ma funkcje wirtualne to nie ma takiej gwarancji.

1

Nie widzę powodu, dla którego ten konkretny przykład miałby crashować: http://melpon.org/wandbox/permlink/StcbbOazzyWO7mak
Inna sprawa, że przy rzutowaniu w dół lepiej używać dynamic_cast i sprawdzić czy się udało.
A jeszcze lepiej nie rzutować w dół.

Jak mam rozwiązać taki problem bez umieszczania funkcji wirtualnej z klasie bazowej?
Jeśli klasa bazowa nie ma tych metod i musiałbyś sztucznie dodać to znaczy że klasa pochodna nie powinna dziedziczyć po tej bazowej.

4
twonek napisał(a)

dlaczego reinterpret_cast ma dostać crasha gdy C-cast nie?

Odwołanie pochodna2->Funkcja po reinterpret_cast to UB.

Dostajesz krasza bo wskaźniki po prawidłowej konwersji nie są (nie muszą być) równe.

int main() {
    KlasaPochodna* pochodna = new KlasaPochodna;
    KlasaBazowa* bazowa = pochodna; 
    KlasaPochodna* pochodna2 = (KlasaPochodna*)bazowa;

    printf("pochodna\t%p\n", pochodna);
    printf("bazowa\t\t%p\n", bazowa);
    printf("pochodna2\t%p\n", pochodna2);
    return 0;
}

Przykładowy wynik (Visual C++ 2015)

pochodna        011E9FD8
bazowa          011E9FDC
pochodna2       011E9FD8

Jest to związane z tym że klasa pochodna zawiera funkcję wirtualną, co powoduje dodanie do klasy tzw. vtable.

reinterpret_cast tylko uspokoi kompilator, a nie poprawi wartości wskaźnika. Stąd błąd.

1
twonek napisał(a):

Nie widzę powodu, dla którego ten konkretny przykład miałby crashować: http://melpon.org/wandbox/permlink/StcbbOazzyWO7mak

Wyjaśnię co się tak naprawdę dzieje:

class KlasaBazowa
{
    int i,j;
};
 
class KlasaPochodna : public KlasaBazowa 
{
    virtual void Funkcja() { }
};

KlasaBazowa w pamięci wygląda tak:

struct KlasaBazowa_impl
{
    int i;  // <-- KlasaBazowa* wskazuje tutaj
    int j;
}

Czyli nic się specjalnego nie dzieje, a sizeof(KlasaBazowa) jest równe 2*sizeof(int), bo obiekt zawiera tylko te dwa inty. Wskaźnik na KlasaBazowa jest wskaźnikiem na pierwszego inta.

KlasaPochodna wygląda jednak tak:

struct KlasaPochodna_impl
{
    void *vtable; // <-- KlasaPochodna*
    int i;        // <-- KlasaBazowa*
    int j;
}

Obiekt zawiera dodatkowy wskaźnik na tablicę funkcji wirtualnych. Typowo znajduje się on na samym początku, choć standard tego nie precyzuje.

Wskaźnik na KlasaPochodna zawiera adres tego wskaźnika. Żeby otrzymać wskaźnik typu KlasaBazowa, trzeba przenieść wskaźnik na pierwsze pole klasy. Robią to automatycznie static_cast, c-style cast i dynamic_cast, ale NIE robi tego reinterpret_cast.

1 użytkowników online, w tym zalogowanych: 0, gości: 1