Frage an die OOP C++ Spezialisten ;-)

Vom einfachen Programm zum fertigen Debian-Paket, Fragen rund um Programmiersprachen, Scripting und Lizenzierung.
Antworten
Belgarad
Beiträge: 749
Registriert: 12.07.2002 02:00:44

Frage an die OOP C++ Spezialisten ;-)

Beitrag von Belgarad » 24.03.2003 15:14:37

Wie kann ich von einem Pointer der auf ein Basisobjekt zeigt, Funktionen eines abgeleiteten Objektes verwenden. Mein ziel ist es ueber einen pointer verschiedene abgeleitete objekte zu initialisieren die alle ueber eine Methode Init verfuegen.

Beispiel:

class basisklasse
{
Init(...)
...
}

class abgeleitet1 : public basisklasse
{
Init(....)
...
}


class abgeleitet2 : public basisklasse
{
Init(....)
...
}



main()
{
basisklasse *bkptr;
abgeleitet1 *ag1ptr;
abgeleitet2 *ag2ptr;


ag1ptr = new(abgeleitet1);
ag1ptr->Init(...) //ruft die korrekte methode Init von abgeleitet2 auf.

ag2ptr = new(abgeleitet2);
ag2ptr->Init(...) //ruft die korrekte methode Init von abgeleitet2 auf.

bpptr = new(abgeleitet1);
bpptr->Init(...) //ruft immer die methode Init der basisklasse auf.
}
Debian SID

Benutzeravatar
walde
Beiträge: 162
Registriert: 24.12.2001 02:22:55
Wohnort: Münster
Kontaktdaten:

Beitrag von walde » 25.03.2003 07:48:48

Ich könnte wetten, Du hast mal Java programmiert.

Funktionüberlagerungen sind soweit ich weiß, in C++ nicht erlaubt. Und sind wohl auch in Java nicht sonderlich gut in der Laufzeit.

C++ erlaubt soweit ich weiß, keine Überlagerung. Also muß Du init virtual deklarieren.

ciao
Walde

Belgarad
Beiträge: 749
Registriert: 12.07.2002 02:00:44

Beitrag von Belgarad » 25.03.2003 08:31:41

habe auch daran gedacht.
nur rufei ch im Init des abgeleiteten objekts die Methode Init des Basisobjektes auf, um eben die felder des Basisobjects zu initialisieren.

Und wenn ich Init virtuell definiere, geht das ja IMHO nicht mehr, oder?
Debian SID

Benutzeravatar
walde
Beiträge: 162
Registriert: 24.12.2001 02:22:55
Wohnort: Münster
Kontaktdaten:

Beitrag von walde » 25.03.2003 17:35:19

Bahnhof ....

Es tut mir leid, ich weiß nicht was Du vorhast.

ciao
Walde

Benutzeravatar
pdreker
Beiträge: 8298
Registriert: 29.07.2002 21:53:30
Lizenz eigener Beiträge: MIT Lizenz
Wohnort: Nürnberg

Beitrag von pdreker » 25.03.2003 17:56:14

Hast Du 'mal versuch, den Pointer vor der Benutzung passend zu casten?
Also statt

Code: Alles auswählen

bpptr->Init(...);
einfach

Code: Alles auswählen

(abgeleitet1*)bpptr->Init(...);
Das erfordert natürlich, dass Du (bzw. der Compiler ;-)) zur Compilezeit weiss, was für ein Objekt er da hat.

Ich bin nicht der grosse C++ Guru, aber soweit ich weiss geht das, was Du vorhast so nicht in C++.

Patrick
Definitely not a bot...
Jabber: pdreker@debianforum.de

Belgarad
Beiträge: 749
Registriert: 12.07.2002 02:00:44

Beitrag von Belgarad » 26.03.2003 10:45:28

ja das mit dem typcast geht, nur will ich das nach moeglichkeit vermeiden.

zum hintergrund:

meine basisklasse sei z.b. geschaeftspartner.
meine abgeleitet1 sei z.b. kunde
meine abgeleitet2 sei z.b. lieferant

nun will ich ein listenobjekt erstellen, das sowohl kunden als auch lieferanten verwaltet.

sowohl kunden als auch lieferanten objekte werden dynamisch mit new erzeugt, und dann mit einer methode INIT initialisiert (damit ich gleich pruefen kann, ob die Initialisierung erfolgreich war).
problem ist:
gemeinsame daten wie firmenname, adresse, tel. etc. habe ich in der basisklasse geschaeftspartenr definiert.
und die besonderheiten dann in kunde und lieferant.

nun hatte ich die idee, bei der objekterstellung immer denselben zeiger zu verwenden (also ein basisklassenzeiger auf geschaeftspartner).
und dann eben INIT aufzurufen, um das object zu initialiesiern.
-virtuell geht "so nicht" weil ich den INIT der basisklasse brauche, um die o.g. datenfelder zu initialisieren.

Aber ich denke mittlerweile dass mein ansatz einfach schlecht gewaehlt ist.

ich glaube ich muss entweder:
1.)die objekte mit einem passenden zeiger referenzieren
oder
2.)typcasten (will ich nicht weil typpruefung des compilers umgehen)
oder
3.)INIT virtuell definieren und in den abgeleiteten klassen auch die basisklassen daten initialisieren.

derzeit tendiere ich zu 1.) bzw. 3.)
und ich denke, ich muss mir nochmal gedanke zum listenobject machen.

wenn jemand ueber eine aehnliche aufgabenstellung gestolpert ist - nur her mit den ideen ;-)
Debian SID

Benutzeravatar
hagish
Beiträge: 36
Registriert: 13.05.2002 19:13:49
Kontaktdaten:

Beitrag von hagish » 26.03.2003 12:23:54

ne dumme frage, wieso nimmst du nicht den konstruktor um die daten der einzelnen klassen zu initialisieren, da kann der parentkonstruktor auch mitaufgerufen werden.
http://www.uwyn.com/resources/uwyn_cpp_ ... /x614.html

Benutzeravatar
pdreker
Beiträge: 8298
Registriert: 29.07.2002 21:53:30
Lizenz eigener Beiträge: MIT Lizenz
Wohnort: Nürnberg

Beitrag von pdreker » 26.03.2003 13:50:47

Hammer OOP Ansatz:

Factory Geschaeftskontakt (erzeugt Geschäftskontakte):
  • public getLieferant() // gibt neuen Geschäftskontakt "Lieferant" zurück
  • public getKunde() // gibt neuen Geschäftskontakt "Kunde" zurück.
  • private getInstance() // gibt generischen Geschäftskontakt zurück
Die zurückgegebenen Objekte sind alle vom gleichen Typ, nur mit passenden Identifiern (private Member + public Testfunktion) versehen. Die Unterscheidung von Kunden und Lieferanten würde ich nicht auf die Objektebene herunterziehen, das ist im Programm besser aufgehoben.

Im Code (betrachte dies als Pseudo Code...)

Code: Alles auswählen

class GK { ... 
	protected int id; // GK_Factory mus hierauf zugreifen können!
	public int getID; // gibt die ID zurück
} // Geschäftskontakt

class GK_Factory {
	public GK getLieferant();
	public GK getKunde();
	private GK get Instance();

	GK getLieferant() {
		GK gk_tmp = getInstance();
		gk_tmp.id = ID_LIEFERANT;
		spezieller Init...
		return gk_tmp;
	}
	GK getKunde() {
		GK gk_tmp = getInstance();
		gk_tmp.id = ID_KUNDE;
		spezieller Init...
		return gk_tmp;
	}

	GK getInstance() {
		GK gk_tmp = new GK;
		allgemeiner Init...
	}
}
Damit hast Du die Abstarktion über die spezielle Art des Geschäftskontakts auf der Softwareebene, und da alle Geschäftskontakte den gleichen Typ (auf Compilerebene) haben, ist es auch kein Problem hier mit Container Klassen alle GKs zusammen handzuhaben, oder sie bei Bedarf schnell Anhand des Id Feldes auszusortieren (oder wenn das zu langsam ist: am Anfang einmal aussortieren und separate Liste immer auf Vorrat halten...)

Patrick
Definitely not a bot...
Jabber: pdreker@debianforum.de

Belgarad
Beiträge: 749
Registriert: 12.07.2002 02:00:44

Beitrag von Belgarad » 27.03.2003 14:27:49

@hagish
habe ich zuerst auch so gemacht.
aber: um eine vernueftige validierung der initialisierungsparameter durchzufuehren, kann man nicht den konstruktor verwenden. denn der gibt ja typischerweise NULL oder einen pointer auf das object zurueck.
daher: erst object erstellen, und dann initialisieren und validieren.

@pdreker:
nice ;-)
scheint so als koennte man das anders nicht loesen (obwohl ich mich vor soetwas druecken wollte).
ich ueberpruefe derzeit das klassenkonzept - mal sehen vielleicht kann ich das ganze besser aufbauen. (z.b. ein containerobeject mit einzelnen first/lastptr fuer jeden objecttype im array ueber ein enum oder so.)

nochmal viele dank an euch - das debian forum ist echt spitze!!!!!!!!
Debian SID

Benutzeravatar
pdreker
Beiträge: 8298
Registriert: 29.07.2002 21:53:30
Lizenz eigener Beiträge: MIT Lizenz
Wohnort: Nürnberg

Beitrag von pdreker » 27.03.2003 14:57:26

Ich würde definitiv auf die Sache mit den Factories gehen (je länger ich darüber nachdenke, desto klarer wird mir das).
Factories sind auch eine absolute Standard-Konstruktion in der OOP und tauchen dauernd auf (Immer wenn man ein Objekt zur Laufzeit "customizen" will) und wenn Du das einmal gemacht hast, wird es immer einfacher...

Das mit der "frisierten Container Klasse würde ich lassen, das ist am Ziel vorbei. Ausserdem brauchst Du dann für Deine Geschäftskontakte evtl. eigene Containerklassen, und kannst dann die anderen (schon vorgegebenen) Container nicht mehr verwenden, was den Code auch nicht gerade schöner macht...

Patrick
Definitely not a bot...
Jabber: pdreker@debianforum.de

Benutzeravatar
walde
Beiträge: 162
Registriert: 24.12.2001 02:22:55
Wohnort: Münster
Kontaktdaten:

Beitrag von walde » 27.03.2003 19:02:22

Es ist richtig, daß Du aus Konstruktoren keinen Wert zurück geben kannst (weder NULL noch einen Pointer noch void AFAIK), allerdings kannst Du noch Ausnahmen verwenden.

ciao
Walde

Belgarad
Beiträge: 749
Registriert: 12.07.2002 02:00:44

Beitrag von Belgarad » 28.03.2003 20:41:42

@pdreker
was mir beim implementieren der "factories" nicht klar ist:
getinstance erzeugt doch eine neues object vom typ gk.
wenn aber lieferant und kunde unterschiedliche datenfelder und moeglicherweise unterschiedliche elementfunktionen kennen, wie erzeuge ich dann ein solches object?

und wie wuerde ich eine individelle methode Init aufrufen koennen? (oder muesste Init dann die id abfragen?)

ich denke das funktioniert nur wenn lieferant und kunde im prinzip dieselben datenfelder und funktionen wie gk verwenden, oder?

(kann aber auch sein dass ich da was missverstanden habe...)



@walde
du hast natuerlich recht. bei einem konstruktor kann man keinen rueckgabewert festlegen. aber das meinet ich auch so, hatte ich "dumm" formuliert.
die "ausnahmen" kenne ich jedoch nicht.
Debian SID

Benutzeravatar
pdreker
Beiträge: 8298
Registriert: 29.07.2002 21:53:30
Lizenz eigener Beiträge: MIT Lizenz
Wohnort: Nürnberg

Beitrag von pdreker » 29.03.2003 12:44:21

Dann musst Du die Klasse GK etwas verkomplizieren:
Du erstellst ein Container Objekt GK_generic:

Code: Alles auswählen

class gk_container {
	int ID;
	GK_Kunde gk_k;
	GK_Lieferant gk_l;
}

Dieses Objekt benutzt Du jetzt als Container, den die Factory zurückgibt. In diesem Container verpackt die Factory dann die jeweiligen speziellen Objekte. Die Factory könnte (sollte?) auch Funktionen haben, die die unverkapselten Objekte zurückgeben, oder eine Funktion, mit der man ein unverkapseltes Objekt "einpacken" kann.

Anhand des ID Feldes kann man zur Laufzeit herausfinden, in welchem der beiden GK_* Member brauchbare Daten stehen.

Es gibt sicherlich noch 3 oder 4 Möglichkeiten das mit sehr ähnlichen Konstruktionen zu lösen.

Patrick
Definitely not a bot...
Jabber: pdreker@debianforum.de

Benutzeravatar
Beelzebub
Beiträge: 24
Registriert: 24.03.2003 00:18:54
Wohnort: Darmstadt

Beitrag von Beelzebub » 31.03.2003 18:41:24

Code: Alles auswählen

#include <iostream.h>
class A {
        public:
                virtual void foo() {
                        cout << "foo\n";
                }
};
class B : public A {
        public:
                void foo() {
                        A::foo();
                        cout << "bar\n";
                }
};
int main() {
        A *gah = new A;
        gah->foo();
        gah = new B;
        gah->foo();
}
liefert erwartungsgemäß
foo
foo
bar
Ja C++ ist eine Krankheit!

Ob hier eine Factory wirklich Sinn ergibt führe ich aus wenn ich mehr Zeit habe.

Belgarad
Beiträge: 749
Registriert: 12.07.2002 02:00:44

Beitrag von Belgarad » 01.04.2003 13:58:50

wie sieht das ganze den aus, wenn ich in der abgeleiteten classe eine andere signatur der elemntfunktion habe?

damit sie ueber einen basisklassenzeiger aufgerufen wird, muesste eine elementfunktion ja als virtual definiert werden.
auf der anderen seite muessen virtuelle funktionen ja dieselbe signatur haben, sonst verdecken sie sie nur die basisklassen-elementfunktion.
Debian SID

Benutzeravatar
Beelzebub
Beiträge: 24
Registriert: 24.03.2003 00:18:54
Wohnort: Darmstadt

Beitrag von Beelzebub » 01.04.2003 14:25:20

Zuerst der wichtigste Tip :
Lerne sinnvolle Objektorientierte Programmierung mit passenden Sprachen. C++ ist dafür IMHO vollkommen ungeeignet. Mein Tip ist hier Eiffel in Verbindung mit "Object Oriented Software Construction" von Meyer.

Redefinition und Überladen von Funktionen beisst sich in gewisser Hinsicht und kann Probleme verursachen.
Wenn man redefinieren möchte sollte man es vermeiden overloading zu betreiben, da das den code sehr konfus macht.

Wenn die Argumente der Funktion unterschiedlich sein müssen, kannste die Polymorphie eh vergessen. Das passt einfach nicht zusammen.

Ein möglicher Ansatzpunkt ist hier evtl. der Konstruktor, da bei der Instanziierung des Objektes ja bekannt sein muss, welches Objekt es denn ist.

Deine Frage zeigt aber, dass du massive Verständnisprobleme mit objektorientierten Prinzipien hast. Hier solltest du dringend "nachbessern" um ein gutes und sinnvolles Klassendesign zu bekommen. OOSC von Meyer (s.o.) ist ein Standardwerk was Objektorientierung betrifft. Als Sahnehäubchen ist die Lektüre von Gamma "Design Pattern" auch sehr zu empfehlen. Meine Empfehlung ist beim Lernen von objektorientierten Prinzipien auf C++ zu verzichten, es beisst sich eben enorm. Wenn man dann objektorientiert denken und designen kann, genau dann kann man auch sinnvoll C++ verwenden (obwohl man dann einen massiven Brechreiz zu erleiden hat :lol:)

Gruß

Beelzebub

Benutzeravatar
Beelzebub
Beiträge: 24
Registriert: 24.03.2003 00:18:54
Wohnort: Darmstadt

Beitrag von Beelzebub » 02.04.2003 12:55:33

So ich habe jetzt etwas mehr Zeit und werde zu einem kleinen Ausflug starten:

Overloading ist ein Relikt aus C. In C gabs nur mehr oder weniger primitive Typen, die keine Erbhierarchie bilden konnten. Beim Aufruf einer Funktion war durch die verwendeten primitiven Typen klar, welche Funktion denn nun genommen werden muss.

In der Objektorientierung begegnen uns nun aber auch Klassen, strenggenommen auf Typen, die voneinander erben können.

A<-B, B erbt von A, B ist ein A. Und genau diese polymorphe Struktur verträgt sich einfach nicht mit Overloading, da man nicht mehr eindeutig bestimmen kann, welche Funktion denn nun genommen werden kann.

Ein Beispiel.

POLYGON <- RECHTECK <- QUADRAT. In normaler dt. Sprache: "Ein Rechteck ist ein Polygon, Ein Quadrat ist ein Rechteck und ein Polygon".

Durch diese Polymorphie kann jetzt natürlich eine Funktion foo(p : Polygon) auch Rechtecke und Quadrate bearbeiten. Und genau diese Mehrdeutigkeit von Typen kann dir beim Overloading zum Verhängnis werden. Es ist nun nicht mehr Eindeutig welche Funktion verwendet werden muss. Im Zweifelsfall nimmt der compiler eben die erste Funktion, die passt, ohne Rücksicht darauf zu nehmen ob eine andere evtl besser passt.

Ganz böse Geschichte.

Gerade dieser Polymorphe Gedanke ist bei den wesentlichen OO Designentscheidungen sehr wichtig. Vererbung ist eben mehr als CodeSharing.

zB möchtest du auch manchmal polymorph tatsächlich die Argumente einer routine ändern. Aber dabei muss eben immer noch sichergestellt sein, dass diese redefinierten routinen auch die Argumente der ursprünglichen routine akzeptieren. Das heisst hier im Klartext, der Typ eines Argumentes kann in der Vererbung "weicher" gemacht werden. Ein Erbe kann mehr Akzeptieren als ein Ahne.

Zusammenfassend: Finger weg vom Overloading in OO Sprachen, auch wenn es angeboten wird.

Ein weiterer grober Schnitzer bei deinem Denkansatz ist die Initialisierung aus dem Konstruktor rauszuziehen. Genau dafür ist ein Konstruktor da, zur primären Initialisierung des Objektes. Zudem kann soetwas wie eine initialisierung mit Parametern nicht polymorph gehandhabt werden. Du brauchst für ein Polygon einfach andere andere Informationen wie bei einem Rechteck. Du musst also wissen was du da genau erzeugen willst, kannst es aber nachher als irgendeinen Superklasse handhaben. Mit dieser Methodik funktionieren dann auch die von pdtreker angesprochenen Factories. Doch das ist ein ganz anderes Thema...

Ich hoffe etwas Klarheit in den Objektorientierten Dschungel gebracht zu haben.

Gruß
Beelzebub[/b]

Antworten