C++ Eine Map mit MapElementen welche eine Abstrakte Klasse ist



  • Hallo liebe C++ Community,

    Ich bin dualer Student und studiere Engineering technischer Systeme mit dem Schwerpunkt Elektrotechnik - Automatisierungstechnik. Wir hatten im letzten Semester die ersten Schritte in C++, wo wir eine Map mit MapElementen als auch Polymorphie betrachtet haben.
    Ich weiß, dass in der C++ STD Library die Map enthalten ist, jedoch sollen wir unsere eigene verwenden. Damit Ihr auf dem Stand seid wie ich:
    Unsere Map:

    #ifndef _MAP_H
    #define _MAP_H
    
    #include "MapElement.h"
    
    template<class t_key, class t_value>
    class Map
    {
    private:
    	MapElement<t_key, t_value>* head;
    	MapElement<t_key, t_value>* tail;
    public:
    	Map(MapElement<t_key, t_value>*);
    	Map();
    	~Map();
    	void append(MapElement<t_key, t_value>*);
    	void print() const;
    
    	class MapIterator
    	{
    	private:
    		MapElement<t_key, t_value>* position;
    	public:
    		MapIterator(MapElement<t_key, t_value>*);
    		MapIterator();
    		~MapIterator();
    		MapIterator& operator++();
    		MapElement<t_key, t_value>* operator*();
    	};
    	MapIterator begin();
    };
    #endif
    

    Die MapElemente:

    #ifndef _MAPELEMENT_H
    #define _MAPELEMENT_H
    
    #include <iostream>
    using std::string;
    
    template<class t_key, class t_value>
    class MapElement
    {
    private:
        t_key m_key;
        t_value m_value;
        MapElement* m_next;
    
    public:
        MapElement(t_key, t_value);
        MapElement();
        ~MapElement();
    
        void print();
        void setKey(t_key);
        void setValue(t_value);
        void setNext(MapElement*);
        t_key getKey();
        t_value getValue();
        MapElement* getNext();
    
        MapElement& operator=(MapElement&);
    };
    
    template<class t_key, class t_value>
    std::ostream& operator<<(std::ostream&, MapElement<t_key, t_value>&);
    
    #endif // _MAPELEMENT_H
    

    Das Testprogramm dazu:

    #include <iostream>
    #include "MapElement.cpp"
    #include "Map.cpp"
    
    int main()
    {
        MapElement<std::string, int> myEle("20BTS", 1);        //Speicherplatzreservierung
        Map<std::string, int> myMap(&myEle);
        MapElement<std::string, int> yourEle =myEle;
        yourEle.setKey("20BTS-MEC");
        MapElement<std::string, int> ele1("20BTS-EAT", 2);
        MapElement<std::string, int> ele2("20BTS-TIN", 3);
        myMap.append(&yourEle);
        myMap.append(&ele1);
        myMap.append(&ele2);
        myMap.print();
    
        std::cout << myEle;
        return 0;
    }
    

    Wir haben die Implementierung auch selbst geschrieben und das Testprogramm funktioniert.

    Nun zu meiner Problem-/Fragestellung:

    In meiner Hausarbeit soll ich das Theorie Wissen in die Praxis in meinen Unternehmen anwenden. Dabei hatte ich die Digitalisierung eines Arbeitsschrrittes im Kopf, wo wir bei unseren Anlagen bei der Hardware Inbetriebnahme die Betriebsmittelkennzeichnung(BMK, erwarteter Datentyp string), Seriennummer(SN, erwarteter Datentyp int) und Messwerte(mw, erwarteter Datentyp double/float) von Temperaturen und Druckaufnehmern aufnehmen, momentan noch auf Blattpapier.
    Dies wollte ich nun mit Hilfe der Map lösen und die MapElemente sollen eine Abstrakte Klasse sein, welche sich in die Unterklassen Temperatur und Pressure aufteilen.

    1. Frage:
      Die Map ist ja eine Template Klasse jedoch weiß ich ja welche Datentypen ich erwarte, sollte ich das ignorieren und die Unterklassen als Template Klassen weiterschreiben?
    2. Frage:
      Ist das überhaupt möglich?

    Ich habe schon ein bisschen Brainstorming betrieben und meine Gedanken in Code ein bisschen zusammengefasst ( Der Code wird nicht funktioren aber ich glaube so versteht man besser meine Gedankenzüge).

    Die Abstrakte/virtuelle Template Klasse MapElement:

    #ifndef _MAPELEMENT_H
    #define _MAPELEMENT_H
    
    #include <iostream>
    using std::string;
    
    template<class t_bmk, class t_sn, class t_measure>
    class MapElement
    {
    private:
    
    public:
        virtual ~MapElement() {}
    
        virtual void print() = 0;
        virtual void setBmk(t_bmk) = 0;
        virtual void setSN(t_sn) = 0;
        virtual void setMW(t_measure) = 0;
        virtual t_bmk getBmk() = 0;
        virtual t_sn getSN() = 0;
        virtual t_mw getMeasure() = 0;
        virtual MapElement* getNext() = 0;
    
        virtual MapElement& operator=(MapElement&) = 0;
    };
    
    template<class t_key, class t_value>
    std::ostream& operator<<(std::ostream&, MapElement<t_key, t_value>&);
    
    #endif // _MAPELEMENT_H
    

    Die abgeleitete Klasse Temperatur:

    #ifndef _TEMPERATUR_H_
    #define _TEMPERATUR_H_
    
    #include "MapElement.h"
    
    template<class t_bmk, class t_sn, class t_measure>
    class Temperatur : public MapElement //Abgeleitete Klasse von MapElement // Eine Konkrete Klasse
    {
    	string bmk;
    	double sn;
    	double mw;
        MapElement* m_next;
    
    public:
    	Temperatur(string, double, double);	// Costum-Konstruktor
    	Temperatur();					// Default-Konstruktor
    
    	~Temperatur();				// Destruktor
    
        virtual void print() override;              // Methoden
        virtual void setBmk(t_bmk) override;
        virtual void setSN(t_sn) override;
        virtual void setMW(t_measure) override;
        virtual t_bmk getBmk() override;
        virtual t_sn getSN() override;
        virtual t_mw getMeasure() override;
        virtual MapElement* getNext() override;
    
        virtual MapElement& operator=(MapElement&) override;
    };
    #endif
    

    Die abgeleite Klasse Pressure:

    #ifndef _PRESSURE_H_
    #define _PRESSURE_H_
    
    #include "MapElement.h"
    
    template<class t_bmk, class t_sn, class t_measure>
    class Pressure : public MapElement //Abgeleitete Klasse von MapElement // Eine Konkrete Klasse
    {
    	string bmk;
    	double sn;
    	double mw;
    	MapElement* m_next;
    
    public:
    	Pressure(string,double, double);	// Costum-Konstruktor
    	Pressure();					// Default-Konstruktor
    
    	~Pressure();				// Destruktor
    
        virtual void print() override;              //Methoden
        virtual void setBmk(t_bmk) override;
        virtual void setSN(t_sn) override;
        virtual void setMW(t_measure) override;
        virtual t_bmk getBmk() override;
        virtual t_sn getSN() override;
        virtual t_mw getMeasure() override;
        virtual MapElement* getNext() override;
    
        virtual MapElement& operator=(MapElement&) override;
    };
    #endif
    

    Ich freue mich auf eure Antworten und den entstehenden Diskurs!
    Mit freundlichen Grüßen
    Ein Neuling in der C++ Welt



  • Uiuiuiui....

    1. Bei den map-Klassen sind die Keys der Elemente konstant, weil intern effiziente Datenstrukturen benutzt werden (zB Red-Black Trees), die äußerst ungehalten reagieren, wenn sich das Suchkriterium plötzlich ändert. Bei eurer Map scheint das aber ausdrücklich erlaubt zu sein. Eure Map ist auch keine Map mit effizientem Zugriff, sondern intern eine verkettete Liste. Keine Ahnung, warum das dann Map genannt wird, vermutlich weil die Elemente einen Key haben, nach dem gesucht werden kann. Mit einer std::map oder std::multi_map hat das aber nix zu tun, das sieht nur ähnlich aus.

    2. Die Umsetzung als Template sollte das Ableiten von MapElement eigentlich überflüssig machen. Du musst dir halt nur Gedanken darüber machen, was der Key und was der Value sein soll. Was macht einen Temperatur- bzw. Druckwert eindeutig? Nimm dieses Attribut und benutz es als Key in der Map. Das könnte dann so aussehen:

    #include <string>
    #include "Map.h"
    
    int main()
    {
       Map<std::string, Temperature> temperatures; // string als Key, Temperature als value
       
       Temperature const t1( "Record #1", 1.0, 2.0 ); // was sind bmk, sn und mw? Aussagekräftige Namen sind ein Muss!
    
       // hier wird´s iwie sperrig, da wäre ne Komfort-Funktion echt gut.
       temperatures.append( MapElement<std::string, Temperature>( t1.getBmk(), t1 ) ); // bmk als Key, Temperaturobjekt als Value
    }
    

    Analog dazu Pressure.

    Lob an euren Dozenten, dass er wenigstens versucht, sowas wie C++ zu machen. Schade nur, dass er auf halbem Weg damit aufhört.

    Edit:
    Ich sehe gerade, dass die Map Pointer auf Objekte speichert. Das ist ein absolutes no-go und katastrophal leichtsinnig. Jetzt muss der Benutzer darauf achten, dass die Elemente, die in der Map "leben", länger leben als die Map selbst, da die Pointer in der Map sonst ungültig werden. Das schreit nach Programmabstürzen und merkwürdigem Verhalten.



  • Warum leitest du von MapElement ab? Eine Ableitung beschreibt eine "Ist ein" Beziehung. Und, platt gesprochen, ein pressure ist kein MapElement.

    MapElement ist templetisiert, damit man da beliebige Datentypen rein schieben kann. Also man zum Beispiel sowas machen könnte:

     MapElement<std::string, Temperatur> ele1;
    

    Sind Druck und Tempertur unabhängig von einander? Also, hast du Betriebsmittelkennzeichnung, Seriennummer und Temperatur, und dann eine andere Kennzeichnung für den Druck? Wenn nicht, hätte ich das in einer Klasse zusammen gefasst.

    Wenn das unterschiedlich ist und du eine gemeinsame Basisklasse benötigst, würde ich da eine eigene Klasse einfügen, z.B. Measurement oder so.

    Wobei ich mit der Benennung eh nicht Glücklich bin. Von einer Klasse Temperature z.B. würde man nicht erwarten, dass es eine Betriebsmittelkennzeichnung und eine Seriennummer gibt, sondern höchsten einen Wert und eine Einheit oder so.



  • Eine eher nicht technischer Hinweis: Als dualer Student kenne ich die Vorgabe (Ich würde es eher Wunsch nennen 😉 ), dass man theoretisches Wissen aus der Uni mit der praktischen Arbeit verbindet.
    Das soll explizit aber nicht heißen, dass man unsinnige Dinge macht, nur damit man "Wissen aus der Theorie" in die Praxis umgesetzt hat. Auf gut deutsch: Du hast selber erkannt, dass deine Map Implementierung zu Lernzwecken dient und es dafür eine (bessere) Klasse in der STD gibt. Von daher solltest du auf der Arbeit (und in jedem anderen Projekt, welches nicht zu Lernzwecken dient) auch die verwenden.

    Persönliche habe ich den "Wunsch" des Verbinden von Theorie und Praxis immer herzlich ignoriert. Das funktioniert nun mal einfach nicht für jedes Thema. Wenn jemand wirklich das mal explizit kritisiert hätte, wären mir aber auch ein paar Ausreden eingefallen.
    Dann ist dein "Theorie in die Praxis umsetzen" eben, dass du C++ schreibst, wie du auch in der Universität gelernt hast etc.



  • @Schlangenmensch
    Das einzige was sich eigentlich verschieden verhält sind halt die Messwerte, bei Temperaturen kalibrieren wir die mit einem Hitzebad und dieser Wert wird notiert. Bei den Druckaufnehmern nehmen wir nur den Funktionsbereich auf z.B. 0-16bar und das unterscheidet sich halt von 141,6°C deswegen dachte ich halt an Polyphormie. Wollte halt das bei der Print oder Set Methode bei Durckaufnehmern nicht rummeckert, dass das halt n Wert y - x bar und einmal bei den Temperaturen halt 136,6°C sei.



  • @Farmer-Zorro
    Macht es wirklich Sinn die verschiedenen Arten von Messwerten in der selben Map zu speichern?
    Kommt mir jetzt eher so vor als ob es besser wäre dafür getrennte Maps zu verwenden.

    Und natürlich frage ich mich ob es überhaupt Sinn macht hierzu etwas in C++ zu programmieren. Ohne weiteres darüber zu wissen würde ich da jetzt eher an Excel denken.



  • @DocShoe
    Der Dozent hat nicht auf dem halben Weg aufgehört das Problem liegt eher am Lehrplan. Der hat halt seine Lehrvorgaben gehabt und musste die in 10 Vorlesungen reinkriegen da wurde vieles wie bspw. die Map nur angekratzt was natürlich auch irgendwo schade ist, ist aber nicht vermeidbar in der Lehre.

    Was die Temperatur und Druckaufnehmer eindeutig macht ist ein der Betriebsmittelkennzeichnung(Bmk) ein Buchstabe der irgendwo in einer Zahlen und Buchstaben Kombination versteckt ist(ist auch von jedem Kunden abhängig wie die Bmks auszusehen haben), für Temperaturen steckt da ein "T" drinnen und für Druckaufnehme irgendwo ein "P". Jedoch sind die Position als auch von den T/P nicht immer die selben.



  • @hustbaer
    Also das Modul hieß "Problemorientierte Programmierung" was halt in C++ in der Industrie gelöst wird. Ob es nun die Programmierung von Steuergeräten ist o.ä. Somit muss ich irgendwas mit C++ machen. Mein Longtermziel wäre es eh gewesen die Map mit den einzelnen Attributen mit Hilfe einer Header File dich ich auch GitHub gefunde habe in ein Excel Dokument auszugeben. Eventuell sogar eine Excel Datenbank anzulegen. Jedoch sprengt das alles irgendwann den Rahmen da ich insgesamt 6 Hausarbeiten anfertigen muss.

    Ich merke gerade das meine Idee eventuell nicht so logisch ist wie ich erhofft hatte und auch nicht unbedingt so umsetzbar ist mit den Methoden und Wissen was mir zur Verfügung steht.



  • Naja, wenn das ganze nur für ne Hausübung ist, dann würde ich sagen: verwende deine Map, aber ohne Polymorphie.
    Entweder mit einer "Messpunkt" Klasse die du sowohl für Temperatur als auch für Druck verwendest. Oder halt mit zwei getrennten "Messpunkt" Klassen, dann aber auch in zwei gertennten Maps.

    Ich würde aber eher nicht empfehlen dann zu versuchen das in der Praxis im Job weiter zu verwenden.



  • @hustbaer
    Ich danke dir für dein/euer Feedback erstmal. Ich lasse mir das nochmal durch den Kopf gehen und gucke ob ich vielleicht auch eine bessere Anwendung finde.
    @DocShoe hatte ja die Standard Map verwendet und als Value Temperatur genommen, kann ich die Standard Map in dieser Form anwenden ->

    Map<std::string (für das BMK), Temepratur/Druck als Value, Messwerte);
    

    wobei Messwerte dann eine Abstrakte Klasse ist?

    Ich bin mit der Funktionsweise der Standard Map nicht betraut und gucke mir die dann die Tage nun genauer.



  • @Farmer-Zorro sagte in C++ Eine Map mit MapElementen welche eine Abstrakte Klasse ist:

    @hustbaer
    Ich danke dir für dein/euer Feedback erstmal. Ich lasse mir das nochmal durch den Kopf gehen und gucke ob ich vielleicht auch eine bessere Anwendung finde.
    @DocShoe hatte ja die Standard Map verwendet und als Value Temperatur genommen, kann ich die Standard Map in dieser Form anwenden ->

    Map<std::string (für das BMK), Temepratur/Druck als Value, Messwerte);
    

    wobei Messwerte dann eine Abstrakte Klasse ist?

    Nein. Aber wieso machst du nicht einfach 2 Maps, eine für Temperatur und eine 2. für Druck?



  • Eine andere Alternative ist eine verschachtelte Map:
    map<string für BMK, map<string für Messwerttyp, float für Messwert>>
    Damit könntest du dann für ein BMK beliebig viele Messwerttypen mit Messwerten abspeichern.

    Ob das aber in dieser Form für dich sinnvoll wäre, weiß ich nicht.

    Oder hast du immer Temp und Druck für eine BMK? Dann ein struct mit den 2 Variablen temp und pressure machen und diese Struct in die Map?

    Da dies eine Hausarbeit ist offenbar mit dem Ziel die Map zu verwenden, kannst du dir vermutlich irgendeine sinnvolle Kombination ausdenken.

    Ich sehe auch erstmal keinen Grund für Polymorphie und habe manchmal den Eindruck, dass es als universelle Waffe für alles (inkl. unpassende Fragestellungen) statt ausschließlich für passende Probleme gelehrt wird.



  • @Farmer-Zorro sagte in C++ Eine Map mit MapElementen welche eine Abstrakte Klasse ist:
    ...

    Was die Temperatur und Druckaufnehmer eindeutig macht ist ein der Betriebsmittelkennzeichnung(Bmk) ein Buchstabe der irgendwo in einer Zahlen und Buchstaben Kombination versteckt ist(ist auch von jedem Kunden abhängig wie die Bmks auszusehen haben), für Temperaturen steckt da ein "T" drinnen und für Druckaufnehme irgendwo ein "P". Jedoch sind die Position als auch von den T/P nicht immer die selben.

    Wenn die Art des Messwerts durch das BMK festgelegt werden, dann können Temperature und Pressure durch den gleichen Datentyp abgebildet werden und brauchen ggf. nur ein weiteres Attribut, das die Art des Messwerts festlegt.

    Oder kommt sowas infrage?

    class MeasurementRecord
    {
       std::string BMK_;
       double Temperature_ = 0.0;
       double Pressure_ = 0.0;
    public:
       MeasurementRecord() = default;
    
       // getter/setter für Bmk, Temperatur und Druck spare ich mir hier
    
       bool has_temperature() const
       {
          return BMK_.find( 'T' ) != std::string::npos;
       }
    
       bool has_pressure() const
       {
          return BMK_.find( 'P' ) != std::string::npos;
       }
    };
    


  • @DocShoe sagte in C++ Eine Map mit MapElementen welche eine Abstrakte Klasse ist:

    bool has_temperature() const
    {
    return BMK_.find( 'T' ) != std::string::npos;
    }

    Ich denke, bei Messwerten könnte man
    a) float nehmen (in der Regel haben die Sensoren eh keine double-Genauigkeit)
    b) NaN für "nicht vorhanden" wählen und sich den String sparen

    NaN wäre meiner Meinung nach auch der sinnvollere Default als 0.0, was ja durchaus (wenn in °C gemessen) eine sinnvolle Temperatur ist. Für K-Slaka wäre das schon extrem kalt... Und mit gettern/settern würde ich für einen Measurement-Record gar nicht erst anfangen. Mögen einige bestimmt anders sehen, ich halte es für unnötigen Boilerplate. Einfach public machen. Fertig.


Anmelden zum Antworten