Frage zu Vererbung und Memberzugriff



  • MisterX schrieb:

    ...
    Klasse2: Erbt diese und hat eine weitere z

    Jetzt möchte ich einen Zeiger vom Typ 1 haben der auf ein Konkretes Objekt vom Typ 2 zeigt.

    Und nun möchte ich die Variable z ausgeben....

    Wie oben schon erwähnt, ist das fachlich gesehen ein "downcast" ... und das macht man prinzipiell in Java genauso wie in C++. In C++ sollte man dafür (wie oben schon erwähnt) dynamic_cast verwenden .... allerdings auf Zeiger und nicht auf Objekte (ist aber auch in Java nicht anders).

    struct A {
        int x,y;
    };
    
    struct B : public A {
        int z;
    };
    
    int main(void) {
        A* a = new B;
        (dynamic_cast<B*>(a))->z = 3;
    ...
    

    s.a.

    http://www.edm2.com/0608/introcpp13.html

    Aber besser ist auf JEDEN Fall der vom Lord vorgeschlagene Weg der virtuellen Funktion/Polymorphie.

    Gruß,

    Simon2.



  • Was meinst du mit vererbe Public?

    Also ich habe es jetzt so:

    class Netz_Nachricht {
    
    public:
    	int groesse; 
    	int MsgTyp;
    
    	Netz_Nachricht() 
        { 
    		MsgTyp=0;
            groesse = sizeof(Netz_Nachricht); 
        } 
    	virtual float gib_Wert();
    
    	int gib_groesse() 
    	{
    		return groesse;
    	}
    
    	int gib_MsgTyp() 
    	{
    		return MsgTyp;
    	}
    
    	virtual ~Netz_Nachricht() {} 
    
    };
    
    class Konkrete_Nachricht1:Netz_Nachricht{
    public:
    
    	float wert;
    
    	Konkrete_Nachricht1    (float wert_)
    	{ 
    		MsgTyp=1;
            wert=wert_; 
            groesse = sizeof(Konkrete_Nachricht1); 
        } 
    
    	float gib_Wert() 
    	{
    		return wert;
    	}
    

    Main:

    #include "Netz_Nachricht.h"
    
    int main() 
    {
    	Netz_Nachricht *n = new Konkrete_Nachricht1(8);
    	return 1;
    }
    

    Fehler:

    :\dokumente und einstellungen\ck\eigene dateien\visual studio 2005\projects\client\main.cpp(7) : error C2243: 'Typumwandlung': Konvertierung von 'Konkrete_Nachricht1 *' zu 'Netz_Nachricht *' ist bereits vorhanden, aber es kann nicht darauf zugegriffen werden.



  • MisterX schrieb:

    Was meinst du mit vererbe Public?

    Etwa so:

    class Konkrete_Nachrricht1 : /*->*/ public Net_Nachricht
    {...};
    

    (standardmäßig erben class's privat - da ist dann keine Typumwandlung möglich)



  • Ich weiß das ich bestimmt schon nerve, aber ich brauche noch mehr Hilfe:

    Jetzt sieht es so aus:

    class Netz_Nachricht {
    
    public:
    	int groesse; 
    	int MsgTyp;
    
    	Netz_Nachricht() 
        { 
    		MsgTyp=0;
            groesse = sizeof(Netz_Nachricht); 
        } 
    	virtual float gib_Wert();
    
    	int gib_groesse() 
    	{
    		return groesse;
    	}
    
    	int gib_MsgTyp() 
    	{
    		return MsgTyp;
    	}
    
    	virtual ~Netz_Nachricht() {} 
    
    };
    
    class Konkrete_Nachricht1:public Netz_Nachricht{
    public:
    
    	float wert;
    
    	Konkrete_Nachricht1    (float wert_)
    	{ 
    		MsgTyp=1;
            wert=wert_; 
            groesse = sizeof(Konkrete_Nachricht1); 
        } 
    
    	float gib_Wert() 
    	{
    		return wert;
    	}
    };
    

    Main:

    int main() 
    {
    	Netz_Nachricht *n = new Konkrete_Nachricht1(8);
    	return 1;
    }
    

    Fehler:

    Main.obj : error LNK2001: Nicht aufgelöstes externes Symbol ""public: virtual float __thiscall Netz_Nachricht::gib_Wert(void)" (?gib_Wert@Netz_Nachricht@@UAEMXZ)".
    C:\Dokumente und Einstellungen\ck\Eigene Dateien\Visual Studio 2005\Projects\Client\Debug\Client.exe : fatal error LNK1120: 1 nicht aufgelöste externe Verweise.



  • in der Main steht noch

    #include "Netz_Nachricht.h"

    das habe ich vergessen mitanzugeben



  • Du hast die Methode gib_wert() in der Basisklasse nur definiert - da brauchst du entweder eine Implementierung dazu oder du markierst sie als pur virtuell, indem du "=0" dahintersetzt.



  • ES geht jetzt, Danke an alle:

    Ich habe noch eine Frage zu virtual:

    Also wenn ich jetzt Konkrete_Nachricht1 und Konkrete_Nachricht2 habe
    und es gibt eine Variable mit zugriffsmethoden, die aber in beiden unterschiedlich ist, muß ich dann beide Zugriffsmethoden Virtual in Netz_Nachricht einfügen?



  • Hi,

    vorab:
    ich habe den Eindruck, Du solltest Dich doch ein wenig mit Tutorials, Büchern, ... beschäftigen. Wenn Du in dem Tempo weitermachst, wirst Du dieses Jahr nicht mehr mit Deinem Program fertig ... (nicht böse gemeint, sondern ein ehrlich gemeinter Ratschlag).
    Ich habe nicht den Eindruck, dass Du wirklich verstehst, was Du da treibst, sondern versuchst, Dich mit trial&error durchzuwursteln und wenn Du über schon diese "Kleinigkeiten" stolperst, wirst Du an den echten Brocken (delete, virtueller Dtor, Kopieren, return-by-value, Referenzen, Initialisierung, overloading, templates, ....) verzweifeln. Sorry, aber C++ ist deutlich komplexer als Java ( = "C++ für Mädchen" 😉 ) und es gibt seeeehr viel mehr darüber zu wissen als Du bis jetzt tust .....und ehrlich gesagt: Ich kann mir nicht vorstellen, dass Du in Java so wirklich sattelfest bist, weil nicht wenige Probleme, die Du hier hast, mit Java genauso wären.

    Zu Deinem Problem: Mit Vererbung sagst Du aus "B ist ein A"; deshalb werden die Dinge, die ein A kann,die aber B spezieller macht, in A als "virtual" deklariert und von B überschrieben.

    Was Du da versuchst, widerspricht dem Konzept ... und das hat nicht nur ästhetische Konsequenzen.

    Mal zwei Fragen:
    - Warum sollen bei Dir überhaupt verschiedene Nachrichtentypen von einer gemeinsamen Basisklasse abgeleitet werden ?
    - Warum soll ein Aufrufer nur eine Basisklasse in die Finger bekommen, dann aber Spezialitäten verschiedener abgeleiteter Klassen nutzen ?

    Das klingt für mich nach schlechtem Design und damit nach "Entwicklunsgfingerbruch und Wartungshölle" 😉

    Gruß,

    Simon2.



  • Es geht darum, das ich versuche mit Sockets Daten zu versenden.
    Da man ja nicht immer das selbe versenden möchte, habe ich die Klasse "Netz_Nachricht" gemacht, welche die eigene Größe und den Typ enthält.

    Wenn ich also Daten empfange, behandle ich sie erst als "Netz_Nachricht"
    lese die ersten 8 byte, also größe und den konkreten Typ aus und kann somit entscheiden, was ich empfangen werde. Dann erzeuge ich mir den passenden Typ "Konkrete_Nachrichtx" und lade die restlichen Daten da rein.

    Wenn es ein besseres Konzept gibt => Bitte immer her damit, aber mir ist nix Besseres eingefallen um verschiedenartige Datenpakete versenden zu können.

    Ach und so ein Anfänger bin ich nicht. Immerhin habe ich schon mit c++ und Opengl ein verteiltes Pong spiel gemacht (es funktioniert sogar halbwegs) und da bin ich auf das Problem mit den versciedenen Daten gestoßen. Ich möchte entweden nur z.B. die Schlägerpositionen versenden oder die Nachricht das der Ball ins Aus ging. Und das Problem war dem Empfänger klarzumachen was denn nun für eine Nachricht kommt. Also habe ich mir gedacht, das jede Nachricht seinen Typ und die eigene Größe angibt. Also past ja Vererbung doch ganz gut.

    Bisher habe ich immer eine Art von Nachricht verschickt, die dann aber alles enthielt auch wenn es nicht interessierte und das erzeugte zu viel Traffic.

    Aber in Java war das mit den Casts irgendwie viel einfacher...aber jetzt weiß ich ja wie es geht.



  • Ah ja,

    so etwas in der Richtung hatte ich mir schon gedacht (wir fluchen immer noch über den externen Kollegen, der vor 5 Jahren dasselbe bei uns verbrochen hat ... übrigens auch mit "Nachrichten"). 😃

    Mein Vorschlag: NICHT vererben.

    Ich würde eine "Nachrichtenerzeuger-Klasse" machen, die den Binärstrom empfängt und anhand der "Kennung" ein Objekt der richtigen Klasse erzeugt. Ob die Ergebnisse selbst noch eine gemeinsame Basisklasse haben, würde ich daran entscheiden, ob sie große Gemeinsamkeiten haben oder nicht. In meinen Ohren klingt es so, als sei das eigentlich nicht der Fall.

    Mit dem Nachrichtenobjekt selbst ist es ja auch noch nicht getan, weil ja irgendjemand etwas mit dieser Nachricht machen soll ... deswegen würde ich das Nachrichtenobjekt (wahrscheinlich eher ein POD/struct) gleich an den Verarbeiter dieser Nachricht weiterleiten - entweder eine Klasse mit entsprechend "overloadeter" ("nicht overridden" !) "verarbeite()"-Methode oder einfach ein Satz "freier Funktionen" ...

    Gruß,

    Simon2.



  • Ich schicke mal den gesamten code bisher (Der grauenhaft ich weiß, aber funzu so halbwegs, dann kanste ja mal konkret verbesserungsvorschläge machen)

    Netznachricht:

    class Netz_Nachricht {
    
    public:
    	int groesse; 
    	int MsgTyp;
    
    	Netz_Nachricht() 
        { 
    		MsgTyp=0;
            groesse = sizeof(Netz_Nachricht); 
        } 
    	virtual float gib_Wert()
    	{
    	return 0;
    	}
    
    	int gib_groesse() 
    	{
    		return groesse;
    	}
    
    	int gib_MsgTyp() 
    	{
    		return MsgTyp;
    	}
    
    	virtual ~Netz_Nachricht() {} 
    
    };
    
    class Konkrete_Nachricht1:public Netz_Nachricht{
    public:
    
    	float wert;
    
    	Konkrete_Nachricht1    (float wert_):wert(wert_)
    	{ 
    		MsgTyp=1;
            groesse = sizeof(Konkrete_Nachricht1); 
        } 
    
    	float gib_Wert() 
    	{
    		return wert;
    	}
    };
    

    Sever.h

    #define Server__ 
    class Server {
    
    public:
    
    	HANDLE hThread[1];
    	DWORD  dwThreadID[1];	
    
    	queue<Netz_Nachricht*> *q;
    
    	Server();
    
    	void beginne_empfang();
    
    };
    
    #endif
    

    Server.cpp:

    #include "server.h"
    
    DWORD WINAPI ThreadFuncServer(LPVOID data){
    
    	long rc;
    	SOCKET acceptSocket;
    	SOCKET connectedSocket;
    	SOCKADDR_IN addr;
    	WSADATA wsa;
    
    	rc=WSAStartup(MAKEWORD(2,0),&wsa);
    	// Socket erstellen
    	acceptSocket=socket(AF_INET,SOCK_STREAM,0);
    
    	// Socket binden
    	memset(&addr,0,sizeof(SOCKADDR_IN));
    	addr.sin_family=AF_INET;
    	addr.sin_port=htons(12345);
    	addr.sin_addr.s_addr=ADDR_ANY;
    	rc=bind(acceptSocket,(SOCKADDR*)&addr,sizeof(SOCKADDR_IN));
    
    	// In den listen Modus
    	rc=listen(acceptSocket,10);
    
    	// Verbindung annehmen
    	connectedSocket=accept(acceptSocket,NULL,NULL);
    
    	queue <Netz_Nachricht*> *q= ((queue<Netz_Nachricht*>*) (data)) ;
    
    	while(rc!=SOCKET_ERROR)
    	{
    		//bisher nur das Senden Implementiert
    		if (!q->empty())
    		{
    			Netz_Nachricht *netz_nachricht = q->front();
    			send(connectedSocket,(const char*)netz_nachricht,netz_nachricht->gib_groesse(),0);
    		}
    	}
    
    		closesocket(acceptSocket);
    		closesocket(connectedSocket);
    		WSACleanup();
    		//DWORD d = (DWORD)data;
    		DWORD d=100;
    		return(d);
    
    }
    
    	Server::Server(){q = new queue<Netz_Nachricht*>();}
    
    	void Server::beginne_empfang()
    	{
    			hThread[1] = CreateThread(NULL,                        
    			0,                            
    			ThreadFuncServer,    //Hier liegt das Problem            
    			(LPVOID)q,           
    			0,                            
    			&dwThreadID[1]);
    			//hier noch weiterschreiben
    
    	}
    

    Client.h

    #include <winsock2.h>
    #include <queue> 
    #include "Netz_Nachricht.h"
    using namespace std; 
    
    #pragma comment(lib, "ws2_32.lib" ) 
    
    #ifndef Client__ 
    #define Client__ 
    class Client {
    
    public:
    
    	HANDLE hThread[1];
    	DWORD  dwThreadID[1];	
    
    	queue<int> *q;
    
    	Client();
    
    	void start();
    
    };
    
    #endif
    

    Client.cpp

    #include "Client.h"
    #include <Iostream>
    
    DWORD WINAPI ThreadFuncClient(LPVOID data)			
    {
    
    long rc;
      SOCKET s;
      SOCKADDR_IN addr;
      WSADATA wsa;
      rc=WSAStartup(MAKEWORD(2,0),&wsa);
      // Socket erstellen
      s=socket(AF_INET,SOCK_STREAM,0);
      // Verbinden
      memset(&addr,0,sizeof(SOCKADDR_IN)); 
      addr.sin_family=AF_INET;
      addr.sin_port=htons(12345); 
      addr.sin_addr.s_addr=inet_addr("192.168.2.101"); 
    
      Netz_Nachricht * nachricht = new Netz_Nachricht();
    
      while(rc!=SOCKET_ERROR)
    	{
    		int wert=0;
    		while (wert<8)
    		{
    			wert = wert+recv (s,(char*)nachricht+wert,8-wert,0);
    
    		}
    
    		int groesse_zwischenspeicher;
    		int typ_zwischenspeicher;
    
    		groesse_zwischenspeicher=nachricht->groesse;
    		typ_zwischenspeicher=nachricht->MsgTyp;
    
    		delete nachricht;
    
    		if (nachricht->MsgTyp==1) 
    		{ 
    			nachricht= new Konkrete_Nachricht1(1);
    		}
    
    			nachricht->groesse=groesse_zwischenspeicher;
    			nachricht->MsgTyp=typ_zwischenspeicher;
    
    			int laenge=0;
    			while (laenge <nachricht->groesse-8)
    			{
    				laenge=laenge+recv(s,(char*)nachricht+laenge+8,nachricht->groesse-(laenge+8),0);
    			}
    
    			cout <<"groesse: "<<nachricht->gib_groesse() << endl;
    			cout <<"Typ: "<<	nachricht->gib_MsgTyp() << endl;
    			cout <<"Wert: "<<	nachricht->gib_Wert() << endl;
    
    			cout <<""<< endl;
    			delete(nachricht);
      }
    
      closesocket(s);
      WSACleanup();
      return((DWORD)data);
    }
    
    Client::Client(){q = new queue<int>();}
    
    	void Client::start()
    	{
    			hThread[1] = CreateThread(NULL,                        
    			0,                            
    			ThreadFuncClient,    //Hier liegt das Problem            
    			(LPVOID)q,           
    			0,                            
    			&dwThreadID[1]);
    			//hier noch weiterschreiben
    
    	}
    

    Ich weiß, dürfen alles Beispiele von: "So macht man es nicht" sein, aber was soll ich konkret anders machen?



  • MisterX schrieb:

    ...was soll ich konkret anders machen?

    Wie gesagt: Erst ein sinnvolles Design überlegen.

    Gruß,

    Simon2.



  • Die hauptidee zum Verbreiten der nachricht war, alles in ne Queue zu stopfen und der Empfänger soll sichs dann rausholen , wenn er es verarbeiten kann.

    (Ich weiß Threadsicher ist das auch (noch) nicht, aber das hätte ich schon irgendwie hingebastelt, da ja nur die Queue in beiden Threads zugriffe hat)



  • Ok, das mit der Nachrichtenerzeuger-Klasse hört sich ja gut an.

    Aber ich habe dazu noch Fragen.

    Also angenommen, ich habe eine Klasse, die Nachrichten erzeugen kann, und eine, die Nachrichten empfangen kann.

    Diese klassen werden also in einem Eigenen Thread laufen.
    Die Empfangsklasse muß ja nachrichten lagern können, da der Verarbeitende Thread ja womöglich nicht schnell genug ist. Also brauche ich nen Container.
    Aber beim Abholen aus dem Container weiß ich ja nicht mehr welche Klasse darin war. (bis auf die Kennung). Also muß die Empfängerklasse sowieso wieder anhand der Kennung die richtige Klasse herstellen und ich habe die Klassenerkennung wieder an 2 Stellen.

    Oder die NachrichtenEmpfangsklasse macht das auch noch. Dann muß ich ja aber für jeden Nachrichtentyp ne eigene get_Methode haben, da der Rückgabetyp ja eindeutig ist. Mache ich das nicht und benutze Tempaltes, muß die Verarbeitende Klasse wieder alles selber richtig herstellen und ich habe das selbe Problem.

    Mit welchem Design kann ich dan umgehen?



  • MisterX schrieb:

    ...Queue zu stopfen und der Empfänger soll sichs dann rausholen , wenn er es verarbeiten kann....Threadsicher...

    Das sind 3 weitere Anforderungen, denen Du mit geeigneten Mitteln zuleibe rücken solltest.
    Wenn Du eine Queue implementieren willst, solltest Du überlegen, eine Klasse/Struct/POD "Queuenachricht" anzulegen, die den Inhalt der Nachricht aufnimmt, und eine "Queueverwaltung", die entsprechend ein put()/get() anbietet.

    Außerdem klingt es so, als sollte es "Listener" geben, die sich bei der Queueverwaltung registrieren und von der bei Eintreffen einer "passenden" Nachricht angestoßen werden. So würde ich das jedenfalls implementerien.
    Die "Queueverwaltung" wäre dann der o.g. "Nachrichtenverwalter/-erzeuger" und die "Listener" wären dann die "Nachrichtenverarbeiter".

    Wenn das Ganze threadsicher sein soll, müsstes Du noch einiges mehr machen ... aber dazu gibt's dicke Bücher.

    Gruß,

    Simon2.



  • Hi,

    die Notwendigkeit eines Containers kommt allerdings nicht durch Multithreading rein, sondern die Anforderung "Queue" beinhaltet die bereits, weil "Queue" immer "asynchrone Verarbeitung" bedeutet .... und das bedeutet eben, dass Schreiber und Leser unabhängig voneinander sind. 😉

    Aber genug der Haarspalterei: Ja, Du brauchst einen Container. 😃
    Ich hätte einen Container von "QueueNachrichten" genommen. Die brauchen nicht viel zu können. Sie müssen nur wissen, wie lang sie sind und welche ihre "identifizierenden Bytes" sind.
    Du mußt fachlich entscheiden, wieviel Konsistenzcheck du in der Queue haben möchtest: Soll "alles" reingestellt werden können (auf die Gefahr hin, dass Nachrichten da drin vergammeln, ohne dass der "Absender" etwas davon merkt) ?
    Oder soll bereits beim Einstellen eine "später nicht verarbeitbare" Message abgelehnt werden ?
    Wenn Du mit "Listenern" arbeitest, kann der Queueverwalter dafür sorgen, dass nur "geputtet" werden kann, wofür jetzt/jemals ein Listener registriert wurde; aber das nimmt Dir die Chance, "vorab" zu füllen....
    Du kannst natürlich noch konfigurieren, welche Kennung prinzipiell eingestellt werden können ... aber das mußt Du dann auch immer konsistent halten.
    In Deinem Beispiel würde ich Nachrichtenempfang und -sendung in derselben Klasse ("Queueverwalter") implementieren, dann hast Du evtl. Abhängigkeiten immer zusammen.

    Ach ja: Kein C++-Standardcontainer kann von sich aus "asynchron arbeiten"; da brauchst Du anderes für. Schonmal bei streams umgeschaut ? (Man könnte ja z.B. auch ein eine Datei schreiben und lesen ... aber auch da muß man diverse Zugriffe serialisieren)...aber auch den std::Queue kannst Du Dir mal ansehen.

    Gruß,

    Simon2.



  • Ach ja: Kein C++-Standardcontainer kann von sich aus "asynchron arbeiten"; da brauchst Du anderes für. Schonmal bei streams umgeschaut ? (Man könnte ja z.B. auch ein eine Datei schreiben und lesen ... aber auch da muß man diverse Zugriffe serialisieren)...

    Ich habe da ma was gebastel,
    mein erster Versuch mit tempate und mein erster mit critical sections, also wahrscheinlich wieder von der Sorte "Abschreckendes Beispiel"

    #include <queue> 
    
    template<class T> class Queue_Thread_sicher
    
    {
    private:	queue<T> *q ;
    private:	CRITICAL_SECTION CriSect;
    
    public:	Queue_Thread_sicher() 
    	{
    		q= new queue<T>();
    		InitializeCriticalSection(&CriSect);
    	}
    
    public: ~Queue_Thread_sicher() 
    		{
    			DeleteCriticalSection(&CriSect);
    		}
    
    //liefern den Wert des letzten Elements .
    public: T back () 
    		{	 
    			EnterCriticalSection(&CriSect);
    				return q->back();
    			LeaveCriticalSection(&CriSect) ;	
    		}
    
    //gibt zurück, ob die queue leer ist
    public: bool empty()
    		{
    			EnterCriticalSection(&CriSect);
    				return q->empty();
    			LeaveCriticalSection(&CriSect);	
    
    		}
    
    //liefern den Wert des ersten Elements .
    public: T front () 
    		{
    			EnterCriticalSection(&CriSect);
    				return q->front();
    			LeaveCriticalSection(&CriSect) ;	
    		}
    
    //löscht das letzte element
    public: void pop () 
    		{	
    			EnterCriticalSection(&CriSect);
    				q->pop();
    			LeaveCriticalSection(&CriSect) ;	
    
    		}
    
    //Fügt ein Element hinzu
    public: void push (T t) 
    		{
    			EnterCriticalSection(&CriSect);
    				q->push(t);
    			LeaveCriticalSection(&CriSect) ;	
    		}
    
    //gibt die Größe an
    public:  float zize () 
    		{
    			EnterCriticalSection(&CriSect);
    				return q->size();
    			LeaveCriticalSection(&CriSect) ;	
    		}
    };
    

    Klappt das?



  • Hi,

    also ich will jetzt nicht in die ganze Breite der threadsafe-Programmierung eintauchen (bin ich auch nicht der Oberexperte drin).
    Nur soviel:
    - Mit CRITICAL_SECTIONs kenne ich mich nicht aus. Letztlich muß es auf Betriebssytemebene runter gehen. C++ bietet das nicht von sich aus.
    - Man muß darauf achten, dass man auch eine threadsafe-Version des Compilers hat.
    - Wenn Du alle Aufrufe über dieselbe "Ampel" (hier "CriSect") absicherst, unterbindest Du jegliche Parallelverarbeitung - was man selten braucht/will. Du mußt Du schon überlegen, was parallel laufen darf und was nicht (z.B. lesende/schreibende Zugriffe).
    - CriticalSections solltest Du via RAII (-> Technikbegriff nachschlagen) öffenen und schließen. Denk immer dran, dass auch exceptions fliegen können .. und zwar nahezu überall.
    - alle mit new (bzw. new[]) allozierten Ressourcen müssen mit delete (bzw. delete[]) freigegeben werden. => Eigentlich sehe ich hier gar keinen Bedarf für ein Heap-Objekt von queue...

    Kleine Anmerkung: Es ist in C++ unnötig und unüblich, jede Funktion
    a) erneut mit Zugriffsspezifikation auszustatten und
    b) inline zu definieren.

    Gruß,

    Simon2.



  • Hello! kbeaddb interesting kbeaddb site!


Anmelden zum Antworten