Wielodziedziczenie i rzutowanie w górę z void*

0

Witam.
Mam klasę która dziedziczy z dwóch innych ( załóżmy C dziedziczy z A i B ).
Mam wskaźnik void* na obiekt klasy która na pewno dziedziczy z A, ale nie musi dziedziczyć z B (może z dowolnej innej klasy, bądź z żadnej poza A). I teraz chcę ten wskaźnik rzutować na A* i mam problem. Statyczne rzutowanie nie działa, a przy próbie dynamic_cast kompilator zgłasza błąd o void*. W tej chwili obszedłem to trochę brzydką metodą ale bardzo mi się ona nie podoba i dobrze by było to zrobić inaczej.

0

Wyjaśni mi czemu więc to nie jest wskaźnik na A, skoro na ten void pointer na pewno wskazuje to wskazuje na coś co jest pochodną klasy A?
Najwyraźniej sam sobie utrudniasz ten problem.
Poza tym mieszanie wskaźników na klasy z void* to bardzo bardzo zły styl programowania (takie coś się robi niesłychanie rzadko i jedynie, gdy wie się co robi).
Po to masz programowanie zorientowane obiektowo, by na tyle sprytnie zaprojektować hierarchię klas by właśnie takich problemów nie było.

0

Jest tam użyty void* bo obiekt jest przekazywany jako userdata dla Lua (język skryptowy na wszelki wypadek) i inaczej się tego zrobić nie da. Tak samo obiekt klasy C jest tworzony w Lua i przekazywany do c++ właśnie jako void*. Jest to ten niesłychanie rzadki przypadek wykorzystania tego i dokładnie wiem co robię wierzaj mi ;).

Tak więc wracając do pytania i pomijając względy etyczne używania void* da się jakoś ładnie to rzutować ?

0

Jakbyś miał dziedziczenie proste, to powinno działać bez problemu, ale ty masz najwyraźniej dziedziczenie wielobazowe.
Tu zaczynają się schody i bez dokładnej wiedzy co ty właściwie tam masz to będzie to tylko zgadywanka. W każdym razi radzę by w dziedziczeniu wielobazowym klasa A lub jej pochodna byłą zawsze PIERWSZA na liście, wtedy zwykłe static_cast<> powinno zadziałać poprawnie.
Dobrze by też było, żeby klasa A miała wirtualny destruktor (jeśli choć jeden destruktor w hierarchii jest wirtualny, choć w zasadzi najbezpieczniej mieć zawsze wirtualny destruktor).

0

Ale ja nie rozumiem gdzie jest problem ze static_cast. Jak to u ciebie wygląda? Bo przecież takie coś:

#include <iostream>
using namespace std;

class A
{
  private:
    int x;
  public:
    A() { x = 1;}
    void wypisz() { cout<<x; }
};

class B
{
  private:
    int y;
  public:
    B() {y=2;}
};

class C : public A, public B
{
  private:
    int z;
  public:
    C() {z=3;}
};

class D : public A
{
  private:
    int v;
  public:
    D() {v=4;}
};

int main()
{
  void* ptr = new D; //dziedziczy z A, ale nie dziedziczy z B
  void* ptr2 = new C; //dziedziczy i z A i z B
  A* aptr = static_cast<A*>(ptr);
  aptr->wypisz();
  A* aptr2 = static_cast<A*>(ptr2);
  aptr2->wypisz();
  return 0;
}

Spokojnie działa i nie widze problemu w statycznym rzutowaniu na A.

0

Jakbyś miał dziedziczenie proste, to powinno działać bez problemu, ale ty masz najwyraźniej dziedziczenie wielobazowe.

No pisząc wielodziedziczenie miałem na myśli właśnie dziedziczenie wielobazowe.

Dobrze by też było, żeby klasa A miała wirtualny destruktor (jeśli choć jeden destruktor w hierarchii jest wirtualny, choć w zasadzi najbezpieczniej mieć zawsze wirtualny destruktor).

Wszystkie klasy w hierarchii mają wirtualne destruktory.

W każdym razi radzę by w dziedziczeniu wielobazowym klasa A lub jej pochodna byłą zawsze PIERWSZA na liście, wtedy zwykłe static_cast<> powinno zadziałać poprawnie.

Hmm.. to jest pewien sposób ale chyba trochę gorszy od tego który zrobiłem.

i bez dokładnej wiedzy co ty właściwie tam masz to będzie to tylko zgadywanka

//edit
patrz post niżej:
//

Jak pisałem poradziłem sobie z tym trochę na około* i po prostu zastanawiałem się czy jest lepszy sposób.

sprawdzam sobie jakiego dokładnie jest typu dany void (mam bezpieczną możliwość czegoś takiego), rzutuje na ten typ ( jest to na pewno typ pochodny A) i potem z tego typu statycznie rzutuje na A*.

0
Shalom napisał(a)

Ale ja nie rozumiem gdzie jest problem ze static_cast. Jak to u ciebie wygląda? Bo przecież takie coś: (...)

Spokojnie działa i nie widze problemu w statycznym rzutowaniu na A.

A sprawdź sobie jak się zachowuje poniższy kod i jak wygląda pA po rzutowaniu:

class A
{
protected:
    map<string, string> mMapA;
public:

    virtual ~A() {};
    void SetA(string a, string b) { mMapA[a] = b; }

};

class B
{
protected:
    map<string, int> mMapB;
public:

    virtual ~B() {};
    void SetB(string a, int b) { mMapB[a] = b; }

};

class C : public B, public A
{
protected:
public:

    virtual ~C() {};

};

int main(int argc, _TCHAR* argv[])
{
    C* pC = new C();
    pC->SetB("dupa", 1);
    void* pV = pC;
    A* pA = static_cast<A*> (pV);
    pA->SetA("asd","asdasdas");
	return 0;
}
0

Zdecydowanie zamień kolejność klas bazowych w C (tak by A była pierwsza) i static_cast<> będzie działać na 100%.
Żeby zrozumieć skąd się to bierze wyobraź sobie klasę D:

class D : public JaksNieistotnaKlasa , public A {
};

A* tab[10];

tab[0] = new D;
tab[1] = new C;

I żeby to działało tablica musi zapamiętać troszkę inne (z pewnym offsetem) wskaźniki niż te zwracane przez new. Właśnie dlatego, kolejność klas jest tak ważna (zwłaszcza kto jest pierwszy na liście).
Mając wskaźnik void tracisz informację jaki ma być ten offset. Chyba, że przed konwersją do void* (gdzieś u ciebie się to robi automatycznie) wymusisz konwersję na A*, wtedy też powinno zadziałać i to bez zmiany kolejności klas, bo do void* upchniesz wtedy ten offset.
Sorry za skróty myślowe, ale nie chce mi się "książki" pisać :P .

0

To ma sens ale nie jestem w stanie zagwarantować, że w jakimś przypadku nie będę musiał rzutować dodatkowo z void* na JendakRównieIstotnąKlasę.

0

no to bez informacji pobocznych sie nie obejdzie.
dziwie sie, ze masz virtual juz uruchomione, a nie probowales rtti+dynamic_cast (te blędy o ktorych wspominasz to tylko bledy kompilacji.. pokonanie ich to max 3 minuty, wiec NIE probowales na serio dyncastowac).
nie powinienes miec zadnych problemow zeby z void* staticcast na wspolny 'interfejs' (aby wymaganie rtti co do 'klasowosci' wskaznika bylo spelnione) i potem dyncastnac na wlasciwy typ, nie wazne czy to pierwszy, trzeci czy dziesiaty na liscie bazowej..

a jesli beda problemy innego typu z rtti, chociaz ich sobie teraz nie wyobrazam, pozostaja jawne informacje poboczne w postaci nazwy klasy czy tez informacji z LUA o typie obiektu i reczny switch po tym..

*) rozszerzajac przyklad:

#include <iostream>
#include <map>
#include <string>
#include <cassert>

using namespace std;

class IUnknown // :) // ten typ ma tylko jedna racje bytu: to jest java.lang.Object, albo System.Object, albo COMowski IUnknown, stad żartobliwa nazwa. Sztuczna czesc wspolna dla wszystkich, aby wszyscy byli wewnatrz tej samej sciezki dziedziczenia, jakkolwiek, ale aby byli. spec dla rtti
{
public: virtual ~IUnknown() {};
};

class A : public virtual IUnknown // uwaga - virtual - logika hierarchii wymaga, ale nie jest konieczne
{
protected:     map<string, string> mMapA;
public:     void SetA(string a, string b) { mMapA[a] = b; }
            void print(string arg){cout<<mMapA[arg]<<endl;}
};

class B : public virtual IUnknown // uwaga - virtual - logika hierarchii wymaga, ale nie jest konieczne
{
protected:     map<string, int> mMapB;
public:     void SetB(string a, int b) { mMapB[a] = b; }
            void print(string arg){cout<<mMapB[arg]<<endl;}
};

class C : public B, public A
{
};

int main(int argc, char const*const argv[])
{
    C* pC = new C();
    pC->SetB("dupa", 1);

    void* pV = pC;

    IUnknown* tmp = static_cast<IUnknown*>(pV);
    A* pA = dynamic_cast<A*> (tmp);
    B* pB = dynamic_cast<B*> (tmp);
    C* pC2 = dynamic_cast<C*> (tmp);

    cout << tmp << ":" << pA << ":" << pB << ":" << pC2 << endl;
    assert(pC2 == pC);
    assert(pB == static_cast<B*>(pC));
    assert(pA == static_cast<A*>(pC));

    return 0;
}

wynik:

0x804c940:0x804c95c0x804c940

czyli pB i pC2sa prawidlowo === oryginalnemu pC, a pA zostal z offsetowany dzieki informacjom w RTTI.
to jest IMHO jedyny przypadek, w ktorym bezpiecznie i prawidlowo z void* bedziesz mogl rzutowac zarowno "od razu" na A, czy na B czy na C.
oczywiscie Od razu, tylko z hopem przez IUnknown..

co do v-inheritance.. jest to dyskutowalne.. jako ze jest to wspolna baza wszystkich klas, pownna byc wspolna i kazdy Twoj obiekt jest jednym obiektem i nie ma uzasadnienia dla nie-wirtualnego podwojnego dziedziczenia dwoch rozlacznych wystapien IUnknown w pojedynczym Twoim obiekcie.. zwlaszcza ze do takiej bazy mozesz wcisnac rozne Tobie menedzerskie rzeczy potrzebne per-obiekt.. jednakze, v-inheritance ma swoj narzut, wiec jak z niego nie skorzystasz, jako ze klasa i tak jest kompletnie pusta, moze byc lepiej usunac v-inheritance. RTTI dziala NADAL bez niej, wynik:

0x804c5c0:0x804c5dc0x804c5c0

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