Problem mit Singleton-Objekt



  • Hallo,

    ich habe eine Frage zu einem Objekt, das mit dem Singleton Pattern erzeugt wird. Es geht um eine Konfigurationsklasse, wobei es von ihr immer nur ein Objekt geben soll. Ich möchte alle meine Konfigurationen ja auch nur in eine Datei speichern.

    Das Objekt wird so angelegt:

    Configuration* Configuration::getInstance() {
      static Configuration* instance;
      if (instance == NULL) {
        logInfo("Creating configuration object");
        instance = new Configuration();
      }
      return instance;
    }
    

    Die Logausgabe wird tatsächlich auch nur ein mal ausgegeben. Das Konfigurationsobjekt hat jetzt ein Attribut vom Typ "std::map". Da werden Konfigurationselemente mit einem "Key" und dem Zeiger auf das Element hineingeladen.

    Zu Beginn meines Programms rufe ich nun auf:

    Configuration::getInstance()->readFromFile(std::string filename)
    

    Die Map hat jetzt ganz viele Zeiger auf Elemente die mit "new" angelegt wurden.

    Alles super, wenn ich direkt eine Zeile danach

    Configuration::getInstance()->saveToFile(std::string filename)
    

    aufrufe, mit einem anderen Dateinamen als Parameter, habe ich die Datei indirekt kopiert. Laden und Speichern klappt!

    Wenn ich nun aber in einem anderen Objekt aus einer ganz anderen Klasse (und in einer anderen .cpp Datei) Konfigurationen verwenden möchte, ist mein Konfigurationsobjekt immer leer! 😞

    Wenn ich explizit noch mal die Datei mit

    Configuration::getInstance()->readFromFile(std::string filename)
    

    in der anderen Klasse einlese, funktioniert es. Das Konfigurationsobjekt existierte aber schon, da die Logausgabe nicht noch mal getätigt wurde!!!

    Warum ist meine Konfiguration bei der Benutzung in einem anderen Objekt leer, obwohl ich ein mal "readFromFile" aufgerufen und Konfigurationselemente angelegt habe? 😕

    Danke danke!

    Viele Grüße,
    anjohn



  • Verwendest du die Configuration Klasse in einem anderen Singleton (oder statischem Objekt)? Es klingt nämlich ein bisschen so, als würdest du das Objekt vor seiner Erzeugung verwenden. (Die Reihenfolge, in der statische und globale Objekte in den verschiedenen Übersetzungseinheiten initialisiert werden, ist undefiniert.)



  • Das Problem liegt nicht in den gezeigten Zeilen. ... Liegt die Implementation von getInstance in einem Header oder in einer cpp-Datei?



  • debugge doch einfach mal, wann die Map geleert wird. Könnte es sein, dass du da vielleicht in SaveToFile einen solchen Seiteneffekt drin hast?



  • Hallo,

    erst mal Entschuldigung, dass ich so spät antworte. Ich habe den Code immer nur verfügbar wenn ich arbeiten bin. Bin HiWi.

    Kellerautomat schrieb:

    Verwendest du die Configuration Klasse in einem anderen Singleton (oder statischem Objekt)? Es klingt nämlich ein bisschen so, als würdest du das Objekt vor seiner Erzeugung verwenden. (Die Reihenfolge, in der statische und globale Objekte in den verschiedenen Übersetzungseinheiten initialisiert werden, ist undefiniert.)

    Das Singleton Objekt wird erst angelegt. Ich bekomme eine Lognachricht dazu. Danach lade ich eine Konfigurationsdatei ein. Dann erstelle ich ein anderes Objekt auf dem Heap und dieses greift dann auf die Konfiguration zu. Das geschieht definitiv nacheinander.

    knivil schrieb:

    Das Problem liegt nicht in den gezeigten Zeilen. ... Liegt die Implementation von getInstance in einem Header oder in einer cpp-Datei?

    In einer Cpp-Datei.

    daddy_felix schrieb:

    debugge doch einfach mal, wann die Map geleert wird. Könnte es sein, dass du da vielleicht in SaveToFile einen solchen Seiteneffekt drin hast?

    Joa, muss ich wohl 😃



  • Also erst mache ich sowas in der Main-Methode:

    Configuration::getInstance()->readFromFile("autosave.ncfg");
    
    Configuration* config = Configuration::getInstance();
    ConfigurationSection* cfgsection = config->getSection("Classifier0");
    
    logInfo("nRefColors in Main: %d",(int)cfgsection->getElement("nRefColors")->getValueAsLonglong(0, ""));
    

    Jetzt steht in meinem Logfile, dass das Element "nRefColors" eine 1 enthält!

    Dann gehts weiter:

    pVision = new Vision(optCam, angleCallback);
    pVision->loadConfig(cfg);
    

    Im pVision-Objekt wird dann wieder ein neues Objekt erzeugt:

    for (unsigned int i = 0; i < NCAMS; i++) {
      // Cam index - swapping cameras here if necessary.
      const unsigned int c = camSwap ? (1 - i) : (i);
      pCamStates[i] = NULL;
      pRGBFilter[i] = new RGBFilter(i);
      ...
    

    Das pVision-Objekt gibt nun den loadConfig-Aufruf an meine pRGBFilter-Objekte weiter:

    void RGBFilter::loadSettings(ConfigTools* cfg,const char* section) {
      stringstream key;
      key << "Classifier" << sectionNumber;
      //Configuration::getInstance()->readFromFile("autosave.ncfg");
      Configuration* config = Configuration::getInstance();
      ConfigurationSection* cfgsection = config->getSection(key.str());
    
      params.nRefColors = (int)cfgsection->getElement("nRefColors")->getValueAsLonglong(0, "");
      logInfo("RefColors = %d", params.nRefColors);
      ...
    

    Jetzt gibt die Logausgabe aber den Defaultwert zurück, nämlich 0. Wenn ich den auskommentierten readFromFile-Aufruf wieder einbinde, funktioniert alles wie erwartet.

    Intern arbeitet meine Konfigurationsklasse mit Maps. Mein Chef hat mich darauf hingewiesen, dass ich mich später in einer anderen Übersetzungseinheit befinde. Ich soll mich mal schlau machen, ob es an den Maps bzw. daran liegt, wie mit ihr Keys verglichen werden.

    Ich habe bis jetzt solche maps:

    map<string, ConfigurationSection*> sectionMap;
    map<string, ConfigurationElement*> elementMap;
    

    Und ich vergleiche nicht vorhandene Objekte so:

    ConfigurationElement* ConfigurationSection::getElement(string key) {
      if (elementMap[key] == NULL) {
        elementMap[key] = new ConfigurationElement(key,this);
      }
      return elementMap[key];
    }
    

    Könnte das an dem internen Vergleich der Map liegen, dass in einer anderen Übersetzungseinheit der getElement-Aufruf den Eintrag in der Map nicht mehr findet?



  • anjohn schrieb:

    ConfigurationElement* ConfigurationSection::getElement(string key) {
      if (elementMap[key] == NULL) {
        elementMap[key] = new ConfigurationElement(key,this);
      }
      return elementMap[key];
    }
    

    Das wird nicht funktionieren. Ein Pointer, dem man nichts zugewiesen hat, ist nicht zwangsläufig ein Nullpointer, sondern er kann überall hinzeigen. map hat aber eine find-Funktion zum Überprüfen, ob es einen Eintrag schon gibt.

    Kann es sein, dass du aus der Java-Ecke kommst? Implizite Nullzeiger und dann überall dieses Pointergefrickel mit new . Lass doch einfach mal überall die Pointer und vor allem die new -Aufrufe weg. Und wenn du schon dabei bist, lass auch noch das Singleton weg. Wenn du nur ein Objekt brauchst, dann erstell auch nur ein Objekt. Kein Grund gleich zu verhindern, dass mehrere Objekte erstellt werden können. Manchmal braucht man sogar noch nicht mal ein Objekt, sondern eine freie Funktion reicht.



  • Michael E. schrieb:

    Kann es sein, dass du aus der Java-Ecke kommst? Implizite Nullzeiger und dann überall dieses Pointergefrickel mit new . Lass doch einfach mal überall die Pointer und vor allem die new -Aufrufe weg. Und wenn du schon dabei bist, lass auch noch das Singleton weg.

    Naja, ich setze gerade nur eine Schnittstelle um. Die Schnittstelle wurde von einem Javaprogrammierer erstellt. Ich habe vor Jahren auch mal Java gelernt, aber mache seit längerer Zeit C++.

    Das mit "find" habe ich umgesetzt. Es hilft mir leider nicht. Auch konnte ich diesem Fehler in einem kleinen Testprogramm nicht reproduzieren. Ich bin wirklich verzweifelt. Debuggen lassen sich solche Maps auch nicht so knorke (zumindest in Netbeans). 😞

    Ich persönlich hätte die ganze Konfigurationsgeschichte am liebsten komplett statisch entworfen. Also wirklich ohne Objekte die ich als Benutzer instanziieren muss.

    Ich habe keine Ahnung, an welcher Stelle plötzlich nichts mehr in meinen Maps steht.



  • Ich habe mich mühseelig mit Logging bis zum Ursprung des Fehlers zurückgehangelt. Ich muss sagen, dass dieser Fehler das Abgefahrendste ist, was mir je untergekommen ist.

    Ich erzeuge meine Config. Dann erzeuge ich ein neues Objekt A. Das Objekt A erzeugt wieder ein neues Objekt B.

    Im Konstruktor von Objekt B rufe ich meine getValue-Funktion auf, alles ok. Dann rufe ich diese Funktion erneut auf und bekomme einen richtigen Wert. Der interne Wert meines Konfigurationselements steht aber plötzlich auf 0. Ich rufe getValue ein drittes mal auf und bekomme 0 zurück, was Sinn macht, da intern im Konfigurationsobjekt eine 0 gespeichert wurde.

    Wie kann das denn?????

    long long ConfigurationElement::getValueAsLonglong(long long defaultValue, string _comment) {
      stringstream ss;
      long long output;
    
      if (value == "") {
        ss << defaultValue;
        ss >> value;
        comment = _comment;
      }
    
      ss.str(value);
      ss >> output;
      return output;
    }
    


  • Deine Ausführungen machen mir den Codeablauf noch nicht klar. Auch weiß ich nicht, warum eine get-Funktion einen Default-Wert und einen Kommentar als Parameter bekommt. Aber wenn du doch schon die genaue Stelle kennst, an der dein Fehler auftritt, dann kannst du wunderbar debuggen.



  • Ich denke mal, dass im Falle eines leeren values das Fehlerflag beim Stream gesetzt ist. Ruf einfach ss.clear() vor ss.str(value); auf.


Anmelden zum Antworten