Werte aufnehmen, als vector speichern und in Klasse verpacken



  • 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