Frage zu Objekten, Referenzen und Vererbung



  • Hallo allerseits,

    ich suche gerade einen Fehler, der sehr mysteriös ist, leider habe ich gerad den Source nicht zur Hand, kann die Klassen aber ggf. nachliefern.

    Folgende Konstellation in Kurzform, damit der Post nicht zu lang wird:

    Abstrakte nicht-statische Basisklasse mit pure virtual DTor und privatem CTor.
    Diese Klasse hat ein nicht-statisches protected Attribute vom Typ einer Struktur (ich gehr hier davon aus, dass nun alle Objekte aller abgeleiteten Klassen eine eigene Instanz dieser Struktur haben und zwar private). Zudem hat diese Basisklasse eine public-Methode, welche die Struktur zurückliefert.
    Eine abgeleitete Klasse A und eine abgeleitete Klasse B. A hat als Attribute eine list von Zeigern auf Objekte von B.
    Die Konstruktoren von A und B rufen in ihrer Initialisierung den CTor der abstrakten Basisklasse auf, dieser verlangt einen string und eine Zahl (weiss den Typ gerade nicht auswendig, die spielt hier auch keine Rolle), welche in das Struktur-Attribute eingetragen werden. Zusätzlich wird noch der Typ übergeben, der ebenfalls in die Struktur übernommen wird. Die abgeleiteten Klassen A und B übergeben unterschiedliche Typen.
    Es gibt zudem eine Klasse C, die eine list von Zeigern der Klasse A als Attribute hat. Es gibt noch ein paar friend-Beziehungen mit anderen Klassen, aber soweit ich weiss nicht zw. A und B. Denke aber, dass friend hier nicht der Verursacher ist!?

    In der Programmklasse wird nun eine Instanz von A erzeugt (beispielhaft):

    A a("eins", 0);
    
    // hier erzeugt die Testausgabe des String aus der Struktur von a: eins
    
    B b1("zwei", 0);
    
    // hier erzeugt die Testausgabe des String aus der Struktur von a: eins
    
    B b2("drei", 0);
    
    // ab hier erzeugt die Testausgabe des String aus der Struktur von a: drei - Wieso? Das ist doch absolut unverständlich! Wie kann b2 a ändern? Und warum nicht schon b1?
    
    a.add(b1);    // für das hinzufügen zur list
    
    // hier erzeugt die Testausgabe des String aus der Struktur von a: drei
    
    a.add(b2);
    
    // hier erzeugt die Testausgabe des String aus der Struktur von a: eins
    
    // nun wird a noch in die Liste der Klasse C gehangen, es ändert sich aber nichts an der Ausgabe!
    

    Also, ich weiss nicht was hier passiert, mit Java ist mir das noch nicht untergekommen (meine ich zu glauben!)!

    Kann mir da jemand Licht im Dunklen zeigen, bevor ich durchdrehe?

    Danke schon mal!

    Ciao


  • Mod

    die beschreibung ist nicht ganz eindeutig. code sagt mehr als tausend worte 😉



  • Also dann los:

    Hier die abstrakte Basisklasse (Header + Impl.):

    #ifndef MENUENTRY_H
    #define MENUENTRY_H
    
    #include <libraries/gadtools.h>
    #include <string>
    
    class MenuEntryC
    {
        friend class MenuC;
        friend class MenusC;
    
    	public:
    		virtual ~MenuEntryC() = 0;
    
        protected:
            struct NewMenu newMenu;
    
    	    MenuEntryC(const int type, const string label);
            struct NewMenu getMenuEntry();
    
        private:
            MenuEntryC();
    };
    
    #endif
    
    // Implementierung
    #include <iostream>
    #include "MenuEntryC.h"
    
    MenuEntryC::MenuEntryC(const int type, const string label)
    {
        cout << "Create MenuEntry: " << this << " Label: " << label.c_str() << endl;
    
        strcpy(newMenu.nm_Label, label.c_str());
        newMenu.nm_Type = type;
        newMenu.nm_CommKey = 0;
        newMenu.nm_Flags = 0;
        newMenu.nm_MutualExclude = 0;
        newMenu.nm_UserData = NULL;
    }
    
    MenuEntryC::~MenuEntryC() {}
    
    struct NewMenu MenuEntryC::getMenuEntry()
    {
        return newMenu;
    }
    

    Hier nun die erste abgeleitete Klasse (wieder mit Header und Impl.):

    #ifndef MENU_H
    #define MENU_H
    
    #include <list>
    
    //#include "MenuC.h"
    #include "MenusC.h"
    #include "MenuEntryC.h"
    
    class MenuC : public MenuEntryC
    {
        friend class MenusC;
    
    	public:
    	    MenuC(const string label, const BOOL enabled = TRUE);
    	    ~MenuC();
    
    	    void addEntry(MenuEntryC *menuEntry);
            void printEntry();
    
    	private:
    	    list<MenuEntryC *> entries;
    
            list<MenuEntryC *>& getMenuEntries();
    };
    
    #endif
    
    // Implementierung
    #include "MenuC.h"
    
    MenuC::MenuC(const string label, const BOOL enabled = TRUE) : MenuEntryC(NM_TITLE, label)
    {
        cout << "Create Menu: " << getMenuEntry().nm_Label << endl;
    }
    
    MenuC::~MenuC() {}
    
    void MenuC::addEntry(MenuEntryC *menuEntry)
    {
        entries.push_back(menuEntry);
    }
    
    list<MenuEntryC *> &MenuC::getMenuEntries()
    {
        return entries;
    }
    
    void MenuC::printEntry()
    {
        cout << "Menu: " << this << " Label: " << this->getMenuEntry().nm_Label << endl;
    }
    

    Und schließlich die 2. Klasse:

    #ifndef MENUITEM_H
    #define MENUITEM_H
    
    #include <string>
    #include "MenuEntryC.h"
    
    class MenuItemC : public MenuEntryC
    {
    	public:
    	    MenuItemC(const string label, const BOOL enabled = TRUE);
    	    ~MenuItemC();
    };
    
    #endif
    
    // Implementierung
    #include "MenuItemC.h"
    
    MenuItemC::MenuItemC(const string label, BOOL enabled = TRUE) : MenuEntryC(NM_ITEM, label)
    {
    }
    
    MenuItemC::~MenuItemC() {}
    

    Im Hauptprogramm passiert dann folgendes:

    MenusC menus;
    MenuC file("File", TRUE);
    
    file.printEntry();    // Ausgabe hier: File
    
    MenuItemC openFile("Open", TRUE);
    
    file.printEntry();    // Ausgabe hier: File
    
    MenuItemC saveFile("Save", TRUE);
    
    file.printEntry();    // Ausgabe hier: Save??? Und bei allen weiteren Ausgaben auch!!!
    

    Alle Ausgaben erfolgen mit der ObjektID von file!

    Ich hab keine Ahnung was hier los ist???!?


  • Mod

    die definition von NewMenu fehlt noch (und ich bin ziemlich sicher, dass der fehler dort liegt - die symptome deuten in eine bestimmte richtung).



  • NewMenu ist eine Struktur des Betriebssystems und dessen API, sie ist in der libraries/gadtools.h definiert:

    struct NewMenu
    {
        UBYTE nm_Type;
        STRPTR nm_Label;
        STRPTR nm_CommKey;
        UWORD nm_Flags;
        LONG nm_MutualExclude;
        APTR nm_UserData;
    };
    

    Wenn gewünscht kann ich auch noch die Beschreibung der Einträge liefern.


  • Mod

    und STRPTR ist ein pointer auf char?



  • Soweit ich weiss, ja.


  • Mod

    dann liegt hier jede menge undefiniertes verhalten vor.

    MenuEntryC::MenuEntryC(const int type, const string label)
    {
        cout << "Create MenuEntry: " << this << " Label: " << label.c_str() << endl;
    
        strcpy(newMenu.nm_Label, label.c_str());
        newMenu.nm_Type = type;
        newMenu.nm_CommKey = 0;
        newMenu.nm_Flags = 0;
        newMenu.nm_MutualExclude = 0;
        newMenu.nm_UserData = NULL;
    }
    

    hier wird nm_label nicht initialisiert - das strcpy schreibt also irgendwo hin.



  • In allen C-Programmbeispielen, die systemkonform programmiert wurden, werden die Arrays aus struct NewMenu wie folgt angelegt:

    struct NewMenu gb_MenuDescribe[] =
    {
      { NM_TITLE, "Project",   NULL, 0, 0, NULL },
      { NM_ITEM,  "Quit",      NULL, 0, 0, NULL },
      { NM_END, NULL, NULL, 0, 0, NULL }
    };
    

    Ich versuche hier eine etwas dynamischere Variante. Im Bsp. hier werden die STRPTR doch auch nirgends initialisiert? Oder wird hierbei der STRPTR auf den Anfang der jeweils übergebenen Zeichenkette gelegt? Dann hab ich aber wieder das Problem aus meinem anderen Post, dass ich eine Struktur nicht in der Initialisierungsliste des CTors initialisieren kann!


  • Mod

    Reth schrieb:

    In allen C-Programmbeispielen, die systemkonform programmiert wurden, werden die Arrays aus struct NewMenu wie folgt angelegt:

    struct NewMenu gb_MenuDescribe[] =
    {
      { NM_TITLE, "Project",   NULL, 0, 0, NULL },
      { NM_ITEM,  "Quit",      NULL, 0, 0, NULL },
      { NM_END, NULL, NULL, 0, 0, NULL }
    };
    

    Ich versuche hier eine etwas dynamischere Variante. Im Bsp. hier werden die STRPTR doch auch nirgends initialisiert?

    Oder wird hierbei der STRPTR auf den Anfang der jeweils übergebenen Zeichenkette gelegt?

    korrekt. der typ eines stringliterals ist ein zeiger auf ein konstansten char, der auf die zeichenkette zeigt.

    Dann hab ich aber wieder das Problem aus meinem anderen Post, dass ich eine Struktur nicht in der Initialisierungsliste des CTors initialisieren kann!

    das musst du ja hier auch nicht tun. und du tust es ja bereits auch für die anderen member von NewMenu nicht. an nm_Label ist nichts besonderes, das dich hindert, ihm auf die selbe weise einen wert zuzuweisen.



  • Hi, danke hast recht.

    Ist mir gestern auch noch eingefallen, sollte dann ca. so funktionieren:

    newMenu.nm_Label = &(label.c_str()[0]);
    

    Wenn ich die Definition von c_str() noch korrekt im Kopf hab.



  • Dir ist schon klar, daß das von label.c_str() zurückgegebene char-Array höchstens so lange existiert wie das label-Objekt, ja? Spätestens wenn du newMenu nach der Destruktion von label verwendest, hast du ein Problem (wohlmöglich sogar deutlich früher).



  • Ja, da ich das Label aber mit:

    MenuC file("File", TRUE);
    

    im Hauptprogramm anlege, sollte es doch so lange existieren, bis file out of scope geht, oder nicht?

    Evtl. wäre es hier noch besser, wenn ich den Labelstring per Referenz übergebe, statt per Copy.

    Ciao



  • Der Text existiert solange bis das temporäre Objekt string( "File" ) out of scope geht. Das ist aber nach dem Semikolon in dieser Zeile. Ab diesem Zeitpunkt ist in einer Struktur tief in der API ein ungültiger Zeiger abgelegt.



  • Hm, das kapier ich jetzt nicht ganz, da das Objekt, welches mit:

    MenuC file("File", TRUE);
    

    angelegt wird doch so lange existiert, bis der umgebende Scope (Methode etc.) verlassen wird? Dachte ich zumindest? Oder nicht?



  • Das Objekt file ja, der String, der aus dem Literal "File" erzeugt wird, nicht.

    Was erwartet der File-Konstruktor? Ein std::string.
    Was gibst Du ihm? Ein const char*.

    Was passiert? Der Compiler entdeckt eine implizite Umwandlung von const char* nach string, bei der der Text kopiert wird. Der Konstruktor von MenuEntryC bekommt einen konstanten std::string, aus dem Du c_str() benutzt. Danach ist der Konstruktor beendet, der konstante std::string, der gebraucht wurde um const char* an MenuEntryC zu übergeben wird gelöscht, und Du stehst da mit einem Pointer, der aus einem c_str() eines jetzt nicht mehr gültigen Objektes stammt.



  • Wie kann ich dann in diesem Fall den String richtig initialisieren, damit er dauerhaft erhalten bleibt, bis file out of scope geht?



  • Übergib doch einfach direkt ein "const char*":

    MenuEntryC::MenuEntryC(const int type, const char* pLabel) 
    { 
        cout << "Create MenuEntry: " << this << " Label: " << pLabel << endl; 
    
        newMenu.nm_Label = pLabel;
        newMenu.nm_Type = type; 
        newMenu.nm_CommKey = 0; 
        newMenu.nm_Flags = 0; 
        newMenu.nm_MutualExclude = 0; 
        newMenu.nm_UserData = NULL; 
    }
    

    Edit: bei den abgeleiteten Klassen (z.B. MenuC) dann natürlich auch...



  • Das geht natürlich auch, wollte aber möglichst mit C++/STL-Konstrukten arbeiten,
    wobei der const char* hier bestimmt performanter ist, mich aber immer an C erinnert.

    Aber ich muss mich wohl damit abfinden, dass die Gemeinsamkeiten zw. C und C++ wohl größer sind als mir lieb wäre. Ich wäre in C++ auch gern ohne Zeiger ausgekommen, aber dann mutieren die STL-Container ja zu Speichermonstern, da immer kopiert wird.

    Ciao



  • Nur der Vollständigkeit halber:

    const char * hat nicht funktioniert, da STRPTR ein char * ist.
    Nachdem ich die Parameter in char * geändert habe, hats bestens funktioniert.


Anmelden zum Antworten