Werte aufnehmen, als vector speichern und in Klasse verpacken



  • Hallo

    rudpower schrieb:

    ich kenn diese Art von Initialisierung gar nicht. Ich initialisiere so: ...

    Nennt sich Initialisierungsliste. Ist deiner Variante vorzuziehen.

    bis bald
    akari



  • der erste Konstruktor ist jetzt klar, dort bekommen die beiden Variablen einfach Anfangswerte zugewiesen.
    Doch der zweite. Hier wird gleich ein objekt erzeugt? und wie ich sehe eine Referenz erstellt.



  • Hallo

    Das ist der Kopierkonstruktor. Dieser wird eingesetzt wenn du schreibst :

    Messwert A(1,2); // Normaler Konstruktor für A
    Messwert B = A; // Kopierkonstruktor für B : kopiert die Werte aus A in B
    

    bis bald
    akari



  • gibts nicht eine einfachere Möglichkeit? Ich sehe keinen Vorteil, im Gegenteil, so wird das Programm doch noch komplexer, vor allem wenn noch mehr Messwerte dazukommen. Ich weiss auch nicht was jetzt im Header steht. In der cpp steht bei mir jetzt:

    Messwerte(double T_, double PH_) :
          temperatur(T_),
          pHWert(PH_) { }
    
    Messwerte(const Messwerte &a) :  //Kopierkonstruktor
          temperatur(a.temperatur),
          pHWert(a.pHWert) { }
    

    Wie geh ich am besten vor?



  • Wieso wird dadurch das Programm komplexer? Die Anzahl der Zuweisungen ändert sich nicht, die Initialisierungsliste wird unbedingt benötigt wenn Basismember oder konstanten Member initialisiert werden müssen. Und den Kopierkonstruktor benötigst du nun mal bei der Copy-Semantik der Standardcontainer.
    Vllt wäre auch ein weiterführendes C++-Buch sinnvoll für dich.



  • witte schrieb:

    Und den Kopierkonstruktor benötigst du nun mal bei der Copy-Semantik der Standardcontainer.

    Jein. Normalerweise erzeugt der Compiler den Kopierkonstruktor und den Zuweisungsoperator automatisch, dabei werden alle Member bitweise kopiert. Borland/Codegear brät da wieder eine Extrawurst, die bei der Verwendung des __property Schlüsselworts für den Zugriff auf Member eine explizite Implementation beider Methoden verlangen.

    Back to topic:
    Die Komplexität eines Kopierkonstruktors im Vergleich zu Threadsynchronisation (die du ebenfalls benötigst) ist quasi null. Da kommt mit Sicherheit noch einiges auf dich zu, das alles andere als trivial ist.



  • hab mir bereits mehrere Bücher besorgt, darunter u.a. das Standardwerk "C++ mit dem Borland C++ Builder" von Richard Kaiser wo ich 70 Euros hingelegt hab. Leider wird das irgendwann sehr tiefgreifend und unverständlich.



  • die Frage ist wie ich das auf 2 Dateien aufteile, also Header und cpp?



  • Naja, das sollte doch hinzukriegen sein 😉
    Woran hakt´s denn konkret?



  • bei mir haperts schon beim Kopierkonstruktor, dessen Verwendung ich nicht verstehe. Ich möchte ja eigentlich nur, dass meine Messwerte in einer Klasse zusammengefasst werden. Wie man eine normale Klasse schreibt, den Konstruktor verwendet versteh ich.



  • Das mit dem Kopierkonstruktor ist eigentlich ganz einfach. Du brauchst einen Kopierkonstruktor nur dann, wenn einer von zwei Fällen auftritt:

    1. Deine Klasse enthält member, die nicht bitweise kopiert werden dürfen, sondern spezielle Massnahmen beim Kopieren erfordern.

    Beispiel (realitätsfern):

    class MyClass
    {
       char* Name_;
    public:
       MyClass( char* Name )
       {
          // Puffer erzeugen und Namen kopieren
          Name_ = new char[strlen( Name +1 )];
          strcpy( Name_, Name );
       }
    
       ~MyClass()
       {
          delete[] Name_;
       }
    };
    
    int main()
    {
       MyClass obj1;
       MyClass obj2 = obj1;
    }
    

    Ohne speziellen Zuweisungsoperator/Kopierkonstruktor passiert hier Folgendes: Bei der Erzeugung von obj2 wird der Kopierkonstruktor benutzt, da er nicht explizit implementiert ist erzeugt der Compiler ihn automatisch. Der automatisch erzeugte Kopierkonstruktor kopiert alle member bitweise, was zur Folge hat, dass der Pointer Name_ aus obj1 und Name_ aus obj2 auf den gleichen Speicherbereich zeigen. Beim Verlassen der main() Funktion wird obj2 zerstört, was ein delete[] auf den Pointer Name_ zur Folge hat und den Speicher wieder freigibt. Dann wird obj1 zerstört, was ebenfalls ein delete[] auf Name_ bedeutet. In diesem Fall wird der gleiche Speicherbereich zwei Mal freigegeben (weil obj1.Name_ und obj2.Name_ auf die gleiche Adresse zeigen), was Undefinierte Verhalten nach sich zieht. Um das zu verhindern muss der Kopierkonstruktor und der Zuweisungsoperator diesen Sonderfall verhindern, was dann so aussähe:

    class MyClass
    {
      // Code von oben steht hier
      MyClass( const MyClass& op ) 
      {
         // tiefe Kopie erzeugen und nicht nur Pointer übernehmen
         Name_ = new char[strlen( op.Name_ ) +1];
         strcpy( Name_, op.Name_ );
      }
    
      MyClass& operator=( const MyClass& op )
      {
         // Selbstzuweisung verhindern
         if( this != &op )
         {
            // Kopiervorgang exception-safe machen durch Verwendung
            // einer temp. Variablen
            char tmp = new char[strlen( op.Name_ ) +1];
            strcpy( tmp, op.Name_ );
            delete[] Name_;
            Name_ = tmp;
         }
         return *this;
      }
    };
    

    Als Faustregel gilt: Wenn man einen speziellen Destruktor, Kopierkonstruktor oder Zuweisungsoperator braucht, dann braucht man alle drei.

    1. Deine Klasse enthält Attribute, die mit dem Schlüsselwort __property zugänglich gemacht werden. Aus irgendwelchen Gründen verweigert der Compiler dann die automatische Erzeugung des Kopierkonstruktors und des Zuweisungsoperators (die er sonst automatisch erzeugen würde). Ist eine reine Fleissarbeit, bei der man lediglich alle member kopieren muss. Vielleicht hat audacia ein Ahnung, warum der KK und ZO nicht automatisch erzeugt werden kann.
    class MyClass
    {
       double ValueA_;
       double ValueB_;
    public:
       MyClass() : 
          ValueA_( 0.0 ), 
          ValueB_( 0.0 )
       {
       }
    
       MyClass( const MyClass& op ) : 
          ValueA_( op.ValueA_ ), 
          ValueB_( op.ValueB_ )
       {
       }
    
       MyClass& operator=( const MyClass& op )
       {
          ValueA_ = op.ValueA_;
          ValueB_ = op.ValueB_;
          return *this; 
       }
    
       // Grund für den ganzen Humbug:
       __property double ValueA = { read=ValueA_, write=ValueA_ };
       __property double ValueA = { read=ValueB_, write=ValueB_ };
    };
    

    Zu deiner Frage:
    Pack die Deklaration der Messwert Klasse in eine Datei (z.B. Messwert.h) und die Implementation in die entsprechende .cpp Datei (Messwert.cpp). Überall, wo du nun die Klasse Messwert benutzen willst fügst du die include Anweisung für Messwert.h ein.



  • verstanden hab ich das noch nicht.
    warum müssen die objekte (das wort member steht wohl für Objekte?) kopiert werden? Ich will doch nur meine Messdaten in einer Klasse haben. Warum reicht es da nicht einfach alle Messdaten einfach nur in die Klasse aufzunehmen?



  • rudpower schrieb:

    verstanden hab ich das noch nicht.
    warum müssen die objekte (das wort member steht wohl für Objekte?) kopiert werden? Ich will doch nur meine Messdaten in einer Klasse haben. Warum reicht es da nicht einfach alle Messdaten einfach nur in die Klasse aufzunehmen?

    Member sind die Elemente einer Klasse, in meinem Beispiel oben hat die Klasse MyClass genau zwei member: ValueA_ und ValueB_.
    Sie müssen kopiert werden, weil std::vector (und eigentlich alle STL Container) intern mit Kopien arbeiten. Du fügst mit push_back() nicht das übergebene Objekt ein, sondern std::vector erzeugt von dem übergebenen Objekt eine Kopie und legt diese im internen Puffer ab.
    Was ich jetzt noch dazu sagen soll weiss ich auch nicht mehr, da fehlt wohl einiges an Grundlagen. Besorg dir mal ein C++ Buch, das sich auf den Sprachstandard beschränkt und nicht auf einen bestimmten Compiler abzielt.



  • das hab ich bereits, hab mir von Erlenkötter das C++ Buch besorgt und durchgearbeitet. Da wird aber leider über Vektoren gar nicht gesprochen. Hab auch vom Springer-Verlag das Buch "Die C++ Standardbibliothek". Ist aber was ich beim durchblättern gesehen hab nicht so einfach geschrieben.





  • so noch mal einen alten Thread aufgreifen. Habe es ohne Kopierkonstruktor gemacht, da
    1. meine member bitweise kopiert werden dürfen (sind ja keine Zeiger)
    2. meine Klasse keine Attribute enthält, die mit dem Schlüsselwort __property zugänglich gemacht werden müssen.

    funktionieren tut es so auch. Hier mal meine Code. Da ich noch keine Messwerte habe, habe ich einfach random-werte aus der rand() - Funktion genommen. Später kommen die Werte als String (aber das kann ich ja mit StrToFloat an den Konstruktor übergeben). Was meint ihr zu der Lösung? Kann ich die Klasse so einsetzen oder gibts evtl Verbesserungsvorschläge?

    // Headerdatei der Klasse:
    #ifndef c_MesswerteH
    #define c_MesswerteH
    class c_Messwerte
    {
    	private:
          double m_drehzahl;
          double m_drehmoment;
       public:
    		c_Messwerte(double p_n = 0.0, double p_m = 0.0);
            double GetDrehzahl(void)const;
            double GetDrehmoment(void)const;
    };
    #endif
    
    // cpp-Datei der Klasse:
    #include <vcl.h>
    #pragma hdrstop
    #include "c_Messwerte.h"
    #pragma package(smart_init)
    
    c_Messwerte::c_Messwerte(double p_n, double p_m)
    	: m_drehzahl(p_n), m_drehmoment(p_m) {}
    double c_Messwerte::GetDrehzahl(void)const
    {
    	return m_drehzahl;
    }
    double c_Messwerte::GetDrehmoment(void)const
    {
    	return m_drehmoment;
    }
    
    //cpp Datei der Form1:
    #include <vcl.h>
    #pragma hdrstop
    #include "HO.h"
    //---------------------------------------------------------------------------
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    TF_HO *F_HO;
    
    //---------------------------------------------------------------------------
    __fastcall TF_HO::TF_HO(TComponent* Owner)
    	: TForm(Owner)
    {
    
    }
    //---------------------------------------------------------------------------
    
    void TF_HO::ErfasseMesswerte(const int AnzahlMesswerte)
    {
    	c_Messwerte o_Messwerte(rand(), rand());
    	v_Messwerte.reserve(AnzahlMesswerte);
    	v_Messwerte.push_back(o_Messwerte);
    }
    

    Die Funktion ErfasseMesswerte hab ich als Methode der Form1 (hier F_HO). Macht man das so? Kann ich die Vetordeklaration auch mit in die F_HO-Klasse schreiben?

    // header der F_HO (Form1)
    #ifndef HOH
    #define HOH
    //---------------------------------------------------------------------------
    #include <Classes.hpp>
    #include <Controls.hpp>
    #include <StdCtrls.hpp>
    #include <Forms.hpp>
    #include "c_Messwerte.h"
    #include <vector>
    //---------------------------------------------------------------------------
    class TF_HO : public TForm
    {
    __published:	// Von der IDE verwaltete Komponenten
    private:	// Anwender-Deklarationen
       std::vector<c_Messwerte>v_Messwerte;
    public:		// Anwender-Deklarationen
    	__fastcall TF_HO(TComponent* Owner);
    	void ErfasseMesswerte(const int AnzahlMesswerte);
    };
    //---------------------------------------------------------------------------
    extern PACKAGE TF_HO *F_HO;
    //---------------------------------------------------------------------------
    #endif
    

    ich hab auch mal die Bezeichnung geändert also o für Objekt, v für Vektor, c für Klasse, m für member. Dann blick ich auch eher durch wenns mehr Quellcode wird 😉
    Kann ich zum Auslesen der Werte (sollen später in eine Datei gespeichert werden und angeszeigt werden) die GetMethode nehmen. Habe gelesen, dass man den Zugriff auf private member über methoden vermeiden sollte. Oder macht man das anders?



  • Hallo,

    Das kann man schon so machen.
    Das mit den Prefixen vor den Bezeichnern halte ich eher für keine gute Idee aber das ist Ansichtssache. Google da mal nach Ungarische Notation.
    Der Parameter bei ErfasseMesswerte muss nicht const sein da der ja sowieso kopiert wird.



  • hab mir grad mal die ungarische Notation angesehen. Da muss man sich aber auch erst mal dran gewöhnen. Da müsst ich meine ganzes Programm ändern was ziemlich umständlich sein dürfte, da der Borland Builder 5 glaub ich keine Suchen/Ersetzen-Funktion hat.



  • Hallo

    Braunstein wollte sicher nicht, das du jetzt komplett auf Ungarische Notation umsteigst. Sondern er wollte sicher dich darauf hinweisen, das deine Präfixe bereits an die Ungarische Notation erinnern, und das in der Entwicklergemeinde die Vor- und Nachteile dieses Benennungsstiles sehr kontrovers diskutiert werden.
    Braunstein will eigentlich nur sagen, das er selber deine Präfixe schlecht findet. Aber du darfst deine Variablen in deinem Programm so nennen wie du willst 😉

    bis bald
    akari



  • Genau das wollte ich sagen.
    Dank für die Übersetzung akari. 😉


Anmelden zum Antworten