problem bei objekterzeugung zur Laufzeit - Zeichenprogramm



  • Hallo,
    ich bin gerade dabei eine kleine Zeichensoftware(einfach nur zur Übung) als Konsolenanwendung unter Windows XP SP2 mit MS Visual C++ 2008 Express Edition zu schreiben, mit der man verschiedene geometrische Grundformen zeichnen kann.

    ich versuche gerade eine Funktion zu implementieren, mit der ich zur Laufzeit einige Attribute ändern kann(z.B. Füllfarbe, Größe usw.);

    Das Problem dabei ist Folgendes:Zur Kompile-Zeit kann das Programm natürlich nicht wissen, welche Objekte der Benutzer später erzeugen wird, erst zur Laufzeit. Deshalb können natürlich auch die benötigten Objekte (z.B. Kreise, Linien, ...) erst zur Laufzeit erzeugt werden.

    In der Funktion, die zur Laufzeit die Methoden zur Änderung der Attributwerte ausführen soll, müssen die jeweiligen Methoden ja in Abhängigkeit von der auszuführenden Aktion angegeben werden.

    dabei entsteht folgender Fehler:
    (Code auf das Wesentliche reduziert)

    //beispiel anhand eines kreises:
    class Kreis
    {
    int radius;
    COORD Mittelpunkt;
    int füllfarbe;
    //...
    
    //Konstruktor:
    Kreis(int p_radius, COORD p_Mittelpunkt, int p_füllfarbe)
    {
    füllfarbe=p_füllfarbe;
    Mittelpunkt=p_Mittelpunkt;
    radius=p_radius;
    }
    //Methoden:
    
    void zeichnen()
    {
    //gekürzt da der Codeabschnitt funktioniert
    }
    
    //zB Füllfarbe setzen:
    void setze_füllfarbe(int neue_farbe)
    {
    //code aus api um farbe zu setzen, hier gekürzt da er problemlos funktioniert
    }
    }
    
    int main()
    {
    cout<<"Was möchten Sie tun?(1:zeichnen, 2:farbe ändern)\n";
    int eingabe;
    cin>>eingabe;
    if(eingabe==1)
    {
    //stark vereinfacht:
    cout<<"geben Sie Radius, Füllfarbe und Mittelpunkt ein!\n";
    int radius, füllfarbe, x_mittelpunkt, y_mittelpunkt;
    cin>>radius>>füllfarbe>>x_mittelpunkt>>y_mittelpunkt;
    COORD Mittelpunkt={x_mittelpunkt, y_mittelpunkt};
    
    //aufruf des Konstruktors:
    Kreis k1(radius, Mittelpunkt, füllfarbe);//initialisierung k1
    }
    
    if(eingabe==2)
    {
    k1.setze_füllfarbe(füllfarbe)/*<---- genau hier entsteht das problem!   */
    }
    }
    

    Der Compiler sagt k1 sei ein unbekannter Bezeichner und .setze_füllfarbe müsse sich auf eine Klasse beziehen. damit hat er ja auch recht da k1 zur kompile-tzeit noch unbakannt ist, aber ich kann das Objekt doch erst zur Laufzeit erstellen!
    Denn der Benutzer wird nicht versuchen Attributwerte eines nicht existierenden Objekts zu ändern und es erst erstellen. Somit ist das Objekt zum Zeitpunkt des Funktionsaufrufes zur Laufzeit dann ja auch rechtzeitig bekannt. Aber dem Kompiler ist das Objekt zur Kompile-zeit ja unbekannt.

    -Kann man so eine Fehlermeldung irgendwie ignorieren oder irgendwie trotzdem kompilern? denn zur Laufzeit würde ja kein Fehler entstehen!

    -ich habe auch schon versucht das problem mit mehrdimmendionalen Arrays zu lösen, erfolglos. denn wenn ich diese auf dem stack allokiere habe ich sehr schnell einen stack overflow und auf dem heap würden sie auch nur viel platz benötigen. außerdem ist der Code mit Klassen einfach vile besser lesbar^^

    kann man also den fehler irgendwie beim Kompilern ignorieren oder das Problem a nderweitig lösen?

    EDIT: das Problem muss aber mit Klassen zu lösn sein da viele Zeichenprogramme mit Klassen arbeiten.

    danke schonmal,
    andi01.



  • Abgesehen von einer Menge Syntax-fehler glaube ich den Fehler gefunden zu haben. Der Compiler kann k1 nicht mehr kennen, da die Variable nicht mehr im Sichtbarkeitsfeld liegt wenn du k1 zum zweiten mal schreibst. Du deklarierst die Variable in der ersten abfrage, doch wenn dieser Scope beendet wird werden auch sämtliche Variablen gelöscht!

    von daher existiert die Variable nicht mehr darum kennt dein Compiler sie nicht, du müsstest also den Kreis vor der ersten abfrage deklarieren.

    Allerdings würde ich dir dringend anraten Vererbung und Polymorphie anzuschauen, damit kannst du dein Prgramm sehr leicht und elegant lösen ohne Codeduplikation oder lästigem Abfragen. Wenn du versuchst mit deiner Methode weitere Graphikklassen hinzuzufügen wirst du sehr viele Probleme bekommen und keinen schönen Code, da du eben nicht weist was der User haben möchte und welche Funktionen er aufruft. Wenn du sämtliche Objekte als Zeiger auf die Basisklasse deklarierst hast du den Vorteil, dass du nicht zu wissen brauchst welches Objekt der User erstellt hat, du brauchst nur die in der Basisklasse definierten virtuellen funktionen aufzurufen und der Rest geschieht vom selben, das richtige Objekt wird verändert/gezeichnet auch wenn du oder dein Code nicht weiß welches Objekt er gerade behandelt.



  • ah ok. Jetzt hab ich das auch verstanden, k1 wurde ja nach Ende der if-Bedingung gelöscht 😃

    das mit den Syntaxfehlern ist klar da der code nicht aus dem programm kopier sondern gester nur schnell geschrieben wurde als Beispiel hier, aber derselbe Fehler besteht tatsächlich auch im Originalcode.

    Und das mit der Vererbung ist so eine Sache da jede Klasse andére Attribute besitzt(z.B, eine Linie keinen Radius) und auch andere Zeichenmethoden braucht usw. deshalb ist der code eigentlich für jede Klasse ganz anders^^ bei der Vererbung würden ja einfach methoden und attribute kopiert werden^^

    du müsstest also den Kreis vor der ersten abfrage deklarieren

    das geht ja aber nicht da der Konstruktor daten des Kreises braucht, die ich erst nach der Abfrage weiß 😞

    Damit kann ich das Objekt erst nach oder während der Abfrage erstellen.

    danke,
    andi01.



  • schau Dir mal "new" und "delete" an..



  • mein Problem ist eigentlich nur, dass das Objekt Kreis mit Ende der if-bedingung den gültigkeitsbereich verlässt, deshalb bkleibt eigtnlich nur eine Frage:

    -wie kann ich innerhalb einer Funktion/Bedingung ein globales Objekt erzeugen??

    (mit new wird das objekt ja nicht global.)

    mfg,
    andi01.



  • Du brauchst kein globales Objekt. new und delete sind schon okay.

    Kreis* k1;
    if (/*...*/)
    {
       // ...
       k1 = new Kreis(/* Konstruktor-Argumente */);
    }
    k1->VerwendeKreis();
    delete k1;
    


  • danke! das war genau das was ich suchte, das Deklerieren vom Aufruf des Konstruktors zu trennen.damit ist dieses Problem gelöst.

    (auch wenn das jetzt nich in diesen thread gehört, aber ich will deswegen keinen neuen eröffnen:)

    noch eine kleine Frage:kann man irgendwie überpfrüfen, ob ein objekt schon existiert?
    (die rage ergab sich aus dem zeichenprogramm, hier ein kleines beispiel:)

    class Kreis
    {
    //...
    };
    
    void kreiszeichnen()
    {
      //objekt erzeugen:
      if(/*objekt kreis 1 existiert schon und kreis 2 noch nicht*/)
      {
        Kreis k2;
      }
      else
      {
        Kreis k1;
      }
    }
    
    int main()
    {
      kreis_zeichnen();
    }
    

    wie kann ich jetzt also prüfen ob es das objekt k1 schon gibt und ich somit mit k2 weitermachen muss?

    danke,
    andi01.



  • Setze den Zeiger auf Null, solange du kein gültiges zugehöriges Objekt hast.

    Und rücke bitte nächstes Mal vernünftig ein.



  • ok, habe ich auch vertanden 🙂 .

    allerdings ist beim test der obigen Methode ein Fehler bei der Kompilierung aufgetaucht:

    Beispiel:

    class Kreis
    {
      public:
      int radius;
      //Konstruktor:
      Kreis(int p_radius)
      {
        radius=p_radius;
      }
      //Beispielmethode:
      void ausmalen()
      {
        //Code zum Ausmalen
      }
    };
    
    Kreis* k1=NULL;
    
    int main()
    {
      //...
      k1=new Kreis(radius);//klappt soweit fehlerfrei
      //nur die folgende zeile mag der Compiler nicht:
      k1.ausmalen();
    }
    

    bei der genannten Zeile sagt der Compiler: error C2228: Links von ".ausmalen" muss sich eine Klasse/Struktur/Union befinden."; k1 ist aber doch Objekt der Klasse Kreis! was ist denn falsch?

    und noch ne ganz kleine Frage: ein Destruktor für die Klasse Kreis ist hier nicht unbedingt nötig, oder?

    mfg,
    andi01.



  • Nein. k1 ist vom Typ: "Zeiger auf Kreis". Das ist völliger Unterschied. 😉

    Aber die Handhabung ist denkbar einfach.

    k1->ausmalen(); // bedeutet: (*k1).ausmalen();
    

    Der -> Operator dereferenziert dir den Zeiger und dann hast du etwas vom Typ "Kreis" und wendet den . Operator drauf an. (Achtung: der -> Operator kann überladen werden und somit theoretisch eine völlig andere Bedeutung haben, als die ausgeschriebene Variante; ist aber eher unüblich, ausser bei Smart Pointer).



  • Und vergiss das delete nicht, wenn du mit new Speicher anforderst!



  • wird der speicher nicht nach programmende automatisch freigegeben? naja, offensichtlich nicht 😃

    ist es dann nicht doch soinnvoll einen destruktor zu schreiben?

    mfg,
    andi01.



  • andi01 schrieb:

    wird der speicher nicht nach programmende automatisch freigegeben? naja, offensichtlich nicht 😃

    Wahrscheinlich schon, aber es ist 1. nicht garantiert, und 2. ist es schlechter Stil und führt nur dazu, dass die Regel an anderen Orten auch nicht beachtet wird. Merk dir einfach, dass zu jedem new ein delete und zu jedem new[] ein delete[] gehört (von Dingen wie Smart-Pointern mal abgesehen).

    andi01 schrieb:

    ist es dann nicht doch soinnvoll einen destruktor zu schreiben?

    Nein, das hat damit nichts zu tun. In der Klasse selbst forderst du nirgends Speicher vom Freestore an, der standardgenerierte Destruktor tut das Richtige.



  • andi01 schrieb:

    wird der speicher nicht nach programmende automatisch freigegeben? naja, offensichtlich nicht 😃

    ist es dann nicht doch soinnvoll einen destruktor zu schreiben?

    mfg,
    andi01.

    Doch wahrscheinlich schon, aber das ist Sache des Betriebssystems, ob das gemacht wird. Also verlass dich nicht drauf!

    Destruktoren machen durchaus Sinn, aber in dem Falle hier bringt dir das nix, weil delete this keine gute Idee ist. (überleg dir dazu, was so aufgerufen wird, wenn ein Objekt zerstört wird. ;))

    Du kannst eine Klasse schreiben, die dir das Obejkt mit new erzeugt und dann im Destruktor das löschen vornimmt. Das ist eine gute Idee und nennt sicht dann Smart Pointer. (google danach, für weiteres).



  • ok, so weit habe ich das jetzt auch verstanden 🙂

    (um keinen neuen thread eröffnen zu müssen die letzte kleine frage bezüglich des Zeichenprogramms auch noch schnell hier 😃 )

    ist es auch möglich den Benutzer zB den Namen eines objekts eingeben zu lassen und das dann zu erzeugen?

    ich dachte mir das etwa so:

    class Kreis
    {
        public:
        int radius;
    };
    
    int main()
    {
        cout<<"Geben Sie den Namen des zu erzeugenden Objekts ein!\n";
        string name;
        cin>>name;
        //Kreis mit eingegebenem Namen erzeugen, ka wie^^(z.B. Kreis name =new Kreis?) 
    }
    

    Hintergrund ist dass es das programm erheblich vereinfachen müsste da ich die ganzen objektnamen nicht selbst vergeben muss und auch nicht bei jedem Objekterstellen den passenden Namen(zB k1/k2/...) suchen müsste^^

    geht das irgendwie?

    mfg,
    andi01.



  • Das grundlegende Prinzip, dass du suchst sind natürlich Arrays. Allerdings bietet sich da wahrscheinlich ein Standardbibliothek Container eher an.

    vector:
    http://www.cplusplus.com/reference/stl/vector/

    oder aber eben, wenn der Name wirklich wichtig ist:

    map:
    http://www.cplusplus.com/reference/stl/map/



  • ich weiß, ich könnte das schon mit einer art arrays lösen. das problem ist das macht den code recht unübersichtlich(in Klassen sind ja zB auch zugehörige methoden/attribute usw. auf den ersten blick erkennbar, das macht den code besser lesbar^^)

    Außerdem würde das doch unglaublich speicher verbrauchen wenn man alle parameter in multidimensionale arrays packt:

    //kreis[name][radius][x_mittelpunkt][y_mittelpunkt][füllfarbe]:
    string kreis [100][100][100][100];
    

    wenn man das mit allen nötigen parameter für alle klassen macht braucht das einiges an speicherplatz^^

    deshalb wollte ih das ganze eigentlich mit Klassen und Objekten lösen, aber das mit dem Namen eingeben funktioniert da wohl definitiv nicht, oder?

    mfg,
    andi01.



  • andi01 schrieb:

    deshalb wollte ih das ganze eigentlich mit Klassen und Objekten lösen, aber das mit dem Namen eingeben funktioniert da wohl definitiv nicht, oder?

    Äh, doch.

    Nicht ohne Grund hat dich drakon auf std::map hingewiesen. Als Ergänzung hilft dir vielleicht dieser Artikel.



  • d.h. wenn das obere Codebeispiel funktionieren soll müsste ich alle Attribute der einzelnen vorherigen Objekte in solchen maps speichern?

    Wenn nicht wie soll das denn dann funktionieren?hätte vielleicht mal jemand in kleines codebeispiel(nur so lang wie oben, das erstellen eines objekts mit dem eingegebenen Namen würde ja schon reichen 🙂 )?

    mfg,
    andi01.



  • andi01 schrieb:

    Außerdem würde das doch unglaublich speicher verbrauchen wenn man alle parameter in multidimensionale arrays packt:

    //kreis[name][radius][x_mittelpunkt][y_mittelpunkt][füllfarbe]:
    string kreis [100][100][100][100];
    

    wenn man das mit allen nötigen parameter für alle klassen macht braucht das einiges an speicherplatz^^

    ?!
    Für etwas hast du ja eine Klasse, wo du die Parameter drin speichern kannst..

    Im übrigen ist das eh nicht das, was du denkst, dass es ist..
    Da wird ein Array von Arrays von Arrays von Arrays erstellt. 100^4. Viel Spass. 😉


Anmelden zum Antworten