PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : C++ / OOP Problem


DocEW
2004-05-26, 23:00:07
Hi,

ich habe folgendes Problem:
Ich habe eine Datenstruktur, die verschiedene Klassen aufnehmen kann, die eine x- und eine y-Koordinate haben. Diese Koordinaten sind zwingend notwendig, da sie für den Aufbau der Struktur benutzt werden.
Ich möchte die Datenstruktur für zwei verschiedene Klassen nutzen. In Java hätte man jetzt vielleicht ein Interface gestrickt, was aus getX() und getY() besteht, ich habe eine Basisklasse gemacht, von der meine beiden Klassen die Methoden erben. Die Datenstruktur operiert dann logischerweise auf der Basisklasse.
Jetzt das Problem: Wenn ich etwas aus der Datenstruktur heraushole, bekomme ich leider nur ein Objekt vom Typ der Basisklasse, weil die Struktur ja nicht weiß, mit was für Objekten ich sie gerade gefüttert habe. Das heißt ich muß casten, und das ist irgendwie unschön. Geht das nicht besser? Als Template geht es wohl nicht, weil ich dann nicht vorraussetzen kann, daß es die beiden Methoden gibt, oder?

DocEW

ScottManDeath
2004-05-26, 23:42:08
Kannst du vielleicht mal etwas code posten. Was du an Deklarationen schon hast und was du damit machen willst; sowie wo du grade casten musst.


Mit template könnte man das eventell machen, da gibts ein paar Tricks...

DocEW
2004-05-27, 13:15:47
Ok, ist wahrscheinlich wirklich etwas schwer verständlich ohne konkreten Code, sorry. Also...

Die Basisklasse:
class Location
{
public:
int y;
int x;
};

Die beiden Klassen, die ich in die Datenstruktur packen will:
class City : public Location
{
// jede Menge anderer Funktionen ...
}

class Facility : public Location
{
// jede Menge anderer Funktionen ...
}

Und schließlich die Datenstruktur, die alles, was x- und y- Koordinate hat, aufnehmen können soll.
class Tree2D
{
public:
Location* getNearestNeighbour(Location *l);
Location* void insert(Location *l);
// jede Menge anderer Funktionen ...

private:
// jede Menge anderer Funktionen ...
};

Das Problem ist: Sobald ich den Tree2D mit irgendwas füttere (=insert), geht die Information verloren, um was es sich gehandelt hat. In meinem konkreten Fall packe ich Facility-Objekte rein und mache dann getNearestNeighbour() mit einem City-Objekt als Parameter und will dann natürlich eigentlich wieder eine Facility rausbekommen. Das ist genau die Stelle, an der ich momentan casten muß.

So, ich hoffe, das ist jetzt etwas klarer! =)

Trap
2004-05-27, 13:36:40
Soweit sieht das richtig aus. Die genau Klasse kann man später wieder mit dynamic_cast rausfinden.
Normalerweise packte man aber Objekte zusammen, wenn einen der genaue Typ nichtmehr interessiert, der Normalfall sollte sein, dass man die virtuellen Funktionen der Basisklasse benutzt und dynamic_cast sollte eine seltene Ausnahme sein.

ScottManDeath
2004-05-27, 19:50:08
@DocEW

Wenn du die die Facility gecasted hast, was machst du damit?

Könntest du die Funktionalität nicht in die Location Klasse packen und virtuell implementieren?

Eine andere Möglichkeit wäre es die getNearestmethode partiell als Template zu machen

Was für einen Compiler nimmst du, VC6 stellt sich z.B. ein bischen arscheckig an, der vom VC7.1 frisst das auch wenn die Methodenrümpfe nicht inline sind.

Anhand des Typs des Parameters ruft der compiler den entsprechenden Code auf.


class Tree2D
{
public:
template<typename T>
T* getNearestNeighbour(T *loc)
{
// nearst suchen, der compiler meldet dir die fehler wenn du ein nicht Location* loc reinschiebst und auf Location* member zugreifst
T* nearest_loc = ......;
return nearest_loc;
}
Location* void insert(Location *l);
// jede Menge anderer Funktionen ...

private:
// jede Menge anderer Funktionen ...
};

micki
2004-05-27, 21:22:15
in C++ macht man interfaces, indem man abstracte klassen deklariert

z.b.



class ITree2D
{
public:
virtual ~ITree2D() = 0;
virtual int X() = 0;
virtual int Y() = 0;
virtual void X(int) = 0;
virtual void Y(int) = 0;
};




dann kannst du fast wie in java, klassen von diesem interface ableiten


class CTree2D : public ITree2D
{
private:
int m_X;
int m_Y;
public:
virtual ~CTree2D(){};
virtual int X(){return m_X;}
virtual int Y(){return m_Y;}
virtual void X(int x){m_X = x;}
virtual void Y(int y){m_Y = y;}
};

class CTown : public ITree2D
{
private:
int m_Strasse;
int m_QuerStrasse;
public:
virtual ~CTown();
virtual int X(){return m_Strasse;}
virtual int Y(){return m_QuerStrasse;}
virtual void X(int x){m_Strasse = x;}
virtual void Y(int y){m_QuerStrasse = y;}
};


und angewendet werden kann das folgendermassen


.
.
.
ITree2D pTree= new CTree2D();
pTree->X(15);
delete pTree;

pTree = new CTown();
pTree->X(32);
delete pTree;
.
.
.



wie du siehst, ist es (bis auf die zeiger zeichen) wie bei Java ;)

MfG
micki

Achill
2004-05-27, 21:58:59
Warum willst du beides (City und Facility) im Tree2D integrieren? Wenn du mit Facility Gebäude in einer Stadt meinst, dann wäre es doch sinniger, einen Tree2D für alle Städt an zu legen und in den Städten jeweil einen Tree2D für alle Gebäude - das wäre aus OO-Sicht logischer. Du ersparst dir das casten und auch eine Suche sollte schneller gehen, da du dich nicht durch Städte und Facilities wühlen musst.

Trap
2004-05-27, 21:59:08
Java-Interfaces in C++ find ich hässlich. Die Funktionen, die man sinnvoll in der Basisklasse implementieren kann, sollte man auch dort implementieren. Sonst muss man wie in Java Copy&Paste-Programmieren.

micki
2004-05-27, 23:13:01
Original geschrieben von Trap
Java-Interfaces in C++ find ich hässlich. Die Funktionen, die man sinnvoll in der Basisklasse implementieren kann, sollte man auch dort implementieren. Sonst muss man wie in Java Copy&Paste-Programmieren.
Das macht man sowieso, interfaces haben einen anderen sinn. deswegen sind die klassen komplett abstract.

MfG
micki

DocEW
2004-05-27, 23:24:22
Original geschrieben von ScottManDeath
@DocEW

Wenn du die die Facility gecasted hast, was machst du damit?

Könntest du die Funktionalität nicht in die Location Klasse packen und virtuell implementieren?

Hmm... nee weiß nicht. Ich füge die Facility einer City hinzu, es gibt aber noch weitere Fälle, wo ich z.B. eine ganze Liste von Facilities aus dem Tree2D hole und weiterverarbeite.

Original geschrieben von ScottManDeath
Eine andere Möglichkeit wäre es die getNearestmethode partiell als Template zu machen

Ok, wenn ich dann einfach einen Compilefehler bekomme, wie du in deinem Code schreibst... könnte ich mal ausprobieren. Ist natürlich nicht wirklich elegant, aber funktioniert! =)

Original geschrieben von ScottManDeath
Was für einen Compiler nimmst du, VC6 stellt sich z.B. ein bischen arscheckig an, der vom VC7.1 frisst das auch wenn die Methodenrümpfe nicht inline sind.

Anhand des Typs des Parameters ruft der compiler den entsprechenden Code auf.

Öhhh, verstehe ich nicht so ganz, was du meinst... jedenfalls benutze ich momentan noch VC6, später soll es aber mit dem GCC laufen.

DocEW
2004-05-27, 23:30:26
Original geschrieben von Achill
Warum willst du beides (City und Facility) im Tree2D integrieren? Wenn du mit Facility Gebäude in einer Stadt meinst, dann wäre es doch sinniger, einen Tree2D für alle Städt an zu legen und in den Städten jeweil einen Tree2D für alle Gebäude - das wäre aus OO-Sicht logischer. Du ersparst dir das casten und auch eine Suche sollte schneller gehen, da du dich nicht durch Städte und Facilities wühlen musst.
Ok, das ist vielleicht etwas missverständlich... machen wir es mal auf Deutsch! =)
Es gibt Städte und, tja ähhh, sagen wir mal Lagerhallen. Beides sind irgendwie Orte, die eine x- und eine y-Koordinate haben.
Es werden eigentlich niemals Facilities und Cities in einem Tree2D gemischt, aber man kann den Tree2D eigentlich für beides nutzen, da er nur die Koordinaten benötigt. Daher fände ich es irgendwie "schade", wenn man diese Eigenschaft programmiertechnisch nicht irgendwie geschickt umsetzen würde. Also dem Tree2D ist es egal, ob man die nächste City zu einer Facility finden will, oder umgekehrt oder wie auch immer. Insofern sollte man ihn auch so benutzen können, wenn man will finde ich.

DocEW
2004-05-27, 23:33:01
@micky:

Ok, aber das löst nicht wirklich das Problem, oder? Anders gesagt: Das Problem hätte ich auch in Java mit Interfaces.

ScottManDeath
2004-05-27, 23:43:47
Das geht beim VC7.1, obs beim GCC geht weis ich nicht

template <class T>
struct S
{
template<class U> void f(U);
};

template<class T> template <class U> void S<T>::f(U)
{ //defined out of line
}



beim VC6 muss das so sein:


template <class T>
struct S
{
template<class U> void f(U)
{
....
}
};


Ich bin mir nicht ganz sicher ob dich das betrifft, die es hier um template member von template Klassen geht.

Du könntest auch eine map<Location*, Facility*> und map<Location*, City*> machen, alle eintragen und mit dem von getNearestNeighbour zurückgegeben Wert in der Map nachgucken ob es eine Facility oder Location war und dann weiterverarbeiten. Ist aber nicht so elegant....

ScottManDeath
2004-05-27, 23:54:51
dann mach doch das ganze als template Klasse, der compiler sagt dir wenn die Typen nicht von Location ableiten, du aber Methoden davon nutzt.


template<typename T>
class Tree2D
{
public:
T* getNearestNeighbour(T*l);
T* void insert(T*l);
// jede Menge anderer Funktionen ...

private:
// jede Menge anderer Funktionen ...
};

ethrandil
2004-05-28, 01:07:45
Original geschrieben von ScottManDeath
dann mach doch das ganze als template Klasse, der compiler sagt dir wenn die Typen nicht von Location ableiten, du aber Methoden davon nutzt.


template<typename T>
class Tree2D
{
public:
T* getNearestNeighbour(T*l);
T* void insert(T*l);
// jede Menge anderer Funktionen ...

private:
// jede Menge anderer Funktionen ...
};


Eher so, oder? (Muss man nicht noch beim Template angeben, dass T von Location erben muss??)


template<typename T>
class Tree2D
{
public:
T* getNearestNeighbour(Location* l);
T* void insert(T*l);
// jede Menge anderer Funktionen ...

private:
// jede Menge anderer Funktionen ...
};

ScottManDeath
2004-05-28, 01:15:23
Kann man so machen, muss man aber nicht; falls der user eine nicht von Location abgeleitete Klasse einfügt und im code z.b. l->getX() steht, sagt das der Compiler.....

Obligaron
2004-05-28, 08:53:04
Dann mach halt einen cast, so schlimm?

Hab aber noch einen anderen Vorschlag, ganz ohne cast, aber relativ für'n popo :D

class Location
{
public:
virtual City* GetMeAsCity(){ return NULL; };
virtual Facility* GetMeAsFacility(){ return NULL; };
virtual bool IsCity()=0;
};

class City : public Location
{
public:
City* GetMeAsCity(){ return this; };
bool IsCity(){ return true; };
}

class Facility : public Location
{
public:
Facility* GetMeAsFacility(){ return this; }
bool IsCity(){ return false; };
}

Benötigt natürlich eine forward deklartion, aber funtzt. Ich würds zwar so net einsetzen, aber ok, geht hier um die technische Machbarkeit (also ganz ohne cast sowas hinzukriegen) :D.

MfG,
Obligaron

DocEW
2004-05-28, 12:10:34
Hehe, gar nicht mal so schlecht! :D

Achill
2004-05-28, 18:11:01
Meine Idee dahinter war nur, das du dir die Auswertung sparen kannst, wenn es logisch getrennt wäre ...

Da du aber wie geschrieben die Datentypen sowieso trennst (du mischst nicht beide) solltest du doch zur jeden Zeit wissen, um was für ein Datentyp es sich handelt und somit auch nach welchen man suchen muss.

Da beide Klassen die Basisklasse Lokation besitzen, kannst du also ohne weiteres eine Suchfunktion für beide schreiben ...

Würde dann eine Klasse vorschlagen, die beide (oder mehr) Tree2D von City- und Facility-Objekten vereint und 2 Funktionen zum suchen des örtlich nächsten anderen Klassentyps besitzt.

DocEW
2004-06-01, 10:19:19
Original geschrieben von Achill
Da du aber wie geschrieben die Datentypen sowieso trennst (du mischst nicht beide) solltest du doch zur jeden Zeit wissen, um was für ein Datentyp es sich handelt und somit auch nach welchen man suchen muss.
Hm, najaaa... also dann müßte ich mir das irgendwie beim erzeugen des Tree2D merken ("ich speichere Facilities") und hinterher immer abfragen. Ist ja auch nicht so schön. Im Grunde funktioniert der Tree ja super, nur vergißt er, was er für Objekte hat, weil es ihn ja gar nicht zu interessieren hat (für seine Funktionen). Das ist quasi das Dilemma. Entweder der Tree weiß Bescheid und ich muß in der Funktionalität im Tree auch unterscheiden, oder eben nicht und ich muß casten.
Vielleicht versuche ich es auch mal so wie ScottManDeath es beschrieben hat.

Auf jeden Fall danke für die Diskussion!!!