Auf gleiche Instanz von verschiedenen Includes zugreifen



  • Hallo,

    ich habe folgendes Problem:

    #ifndef MYCLASS_H
    #define MYCLASS_H
    
    class MyClass {
    
        MyClass(){};
    
    } myclass;
    
    #endif
    
    // Header von anderer Klasse 1
    #include "MyClass.h"
    
    // Mach irgendwas mit der myclass Instanz
    
    // Header von anderer Klasse 2
    #include "MyClass.h"
    
    // Mach irgendwas anderes mit der myclass Instanz
    

    Problem ist jetzt dass der Linker meint: fatal error LNK1169: Mindestens ein mehrfach definiertes Symbol gefunden.

    Also die Instanz myclass ist nach dem Einbinden in Klasse 1 schon vorhanden und daher ist myclass dann beim Einbinden in Klasse 2 mehrfach vorhanden.

    Ich will aber in beiden (oder noch mehreren) Klassen genau nur auf diese eine Instanz zugreifen... Wie erklärt man das dem Compiler?


  • Mod

    Nichts im Header definieren (außer inline und/oder als Template)! Denn sonst hast du es, wie du gerade merkst, in jeder Übersetzungseinheit definiert, wo der Header benutzt wird.
    "Richtig" ("richtig" in Anführungszeichen, weil globale Programmzustände fast immer pfui sind) geht das so: In einer Übersetzungeinheit die Variable definieren, im Header nur bekannt machen (deklarieren). Bekannt machst du eine Variable aus einer anderen Übersetzungseinheit mittels extern .



  • Am besten lässt man so was tunlichst sein. Globale Variablen bringen einen Haufen technischer (Threadsicherheit, Reentrancy) und organisatorischer (benutzender Code macht seine Abhängigkeiten nicht offensichtlich, läuft in Probleme, wenn er gemischt mit anderem Code benutzt wird, der die gleiche Variable benutzt) Probleme mit sich. Gewöhn dir so was besser gar nicht erst an.

    Wenn du dir gut überlegt hast, es trotzdem zu müssen -- es gibt wenige Fälle, in denen man es rechtfertigen kann -- geht das so:

    // header.hpp:
    
    class some_class{ };
    
    extern some_class i_was_warned_not_to_do_this;
    
    // source.cpp:
    
    some_class i_was_warned_not_to_do_this;
    


  • Ok, mit extern funktioniert das schonmal.

    Bezüglich der Problematik mit globaler Variable: Ja, ich hab mir auch schon gedacht dass das vermutlich nicht so optimal ist, aber mir ist nicht eingefallen wie ich das umgehen könnte.

    Also es ist schon so dass alle meine Klassen nur eine Instanz von MyClass verwenden, und zwar alle die Gleiche Instanz. Wenn ich hier jetzt keine globale Variable erstellen würde, dann müsste ich die selbe Instanz ja jedesmal neu erstellen wenn ich sie benötige, oder? Und der Konstruktor ist relativ heavy, von daher würde ich da gerne drauf verzichten...



  • Man könnte (Achtung: könnte) das Singleton-Pattern verwenden. Das hat aber letztlich dieselben Probleme, wie seldon sie beschrieben hat und ist quasi auch nur ein Wrapper um globale Variablen.
    Für viele sind sie in manchen Situation das Heil von Gott persönlich gegeben, um alles damit zu lösen. Und bei uns an der Uni wird es auch munter gelehrt wie toll das doch ist.


  • Mod

    happystudent schrieb:

    Bezüglich der Problematik mit globaler Variable: Ja, ich hab mir auch schon gedacht dass das vermutlich nicht so optimal ist, aber mir ist nicht eingefallen wie ich das umgehen könnte.

    Dann ist deine Ausgangsfrage hier im Thread ein typisches XY-Problem. Du denkst, eine globale Variable wäre das geeignete Mittel zum Lösen deines Problems und fragst dann nach deinem Folgeproblem, das du bei der Benutzung der globalen Variable hattest. Dabei wäre die bessere Frage gewesen, wie man dein Ausgangsproblem optimal löst.

    Also es ist schon so dass alle meine Klassen nur eine Instanz von MyClass verwenden, und zwar alle die Gleiche Instanz. Wenn ich hier jetzt keine globale Variable erstellen würde, dann müsste ich die selbe Instanz ja jedesmal neu erstellen wenn ich sie benötige, oder? Und der Konstruktor ist relativ heavy, von daher würde ich da gerne drauf verzichten...

    Erzähl mal mehr:

    Was modelliert die Klasse?

    Warum muss sie global sein, um von anderswo benutzt zu werden? Es gibt schließlich so etwas wie Parameter.

    Wie kann es einerseits eine Lösung sein, die Instanz jedes Mal neu zu konstruieren, aber andererseits beschreibst du eine gleichwertige Lösung, bei der du jedes Mal die gleiche Instanz zu benutzt? Dies deutet auf ein schweres Designproblem hin, da die Objektidentität unklar zu sein scheint.



  • Ok, also es ist folgendermaßen.

    Die Klasse ist letztendlich ein Ersatz für SendMessage, um mit einem Fenster das in einer DLL definiert, ist zu kommunizieren.

    Die Kommunikation könnte auch normal mittels SendMessage laufen. Um aber Performance zu sparen bietet die DLL auch die Möglichkeit sich die direkten Pointer der WndProc zu holen, so dass kein Umweg über das langsamere Window Message System gegangen werden muss.

    Von diesem Fenster existiert (momentan) nur genau eine Instanz welche direkt beim Start der Anwendung erzeugt wird. In verschiedenen Klassen will ich jetzt mit diesem Fenster kommunizieren, wozu ich immer die selbe Instanz von der Kommunikations-Klasse brauche.

    Ich könnte natürlich auch jedesmal wenn ich kommunizieren will ein neues Object anlegen. Im Konstruktor würde ich dann das Handle auf das Fenster übergeben und der Konstruktor würde sich dann die Funktionspointer (via SendMessage) holen. Dann wäre aber der Vorteil der besseren Performance weg, weil der Konstruktor ja seinerseits SendMessage verwenden würde. Daher will ich dieses Objekt nur einmal anlegen.



  • Ich glaube, wir sind hier im falschen Forum; das klingt alles sehr WinAPI-spezifisch. Hast du nachgemessen, wieviel SendMessage wirklich frisst? Ich kann mir eigentlich nicht vorstellen, dass das sehr lange dauert -- der muss ja eigentlich nur aus dem Objekt, auf das der HWND zeigt, den Funktionszeiger raussuchen und aufrufen. Ich würde da nicht mehr Overhead erwarten als bei einem virtuellen Funktionsaufruf, denn tatsächlicher Aufwand wird ja nur bei PostMessage notwendig (weil das Event dann asynchron verarbeitet wird). Zudem: Ich weiß ja nicht, was du da für eine Applikation baust, aber dass der Event-Dispatch ein Performance-Bottleneck ist, kommt in meiner Erfahrung ausgesprochen selten vor.

    Aber prüfen kann ich das jetzt alles nicht. Nachmessen wär angesagt.

    Ansonsten ist das Schlüsselwort "momentan." Ich würde das, wenn sich die Notwendigkeit bestätigt, im Zweifel so aufziehen, dass die relevanten Klassen statt eines HWND ein Objekt halten, das ein HWND und einen WNDPROC-Zeiger hält, der dann stumpf hin- und herkopiert werden kann. So bist du gleich gerüstet, wenn ein zweites Fenster dazukommt. Was passieren wird. So Dinge passieren immer.



  • Nee, also ich bin schon der Meinung dass das hier im richtigen Forum ist. Das eigentliche Problem ist ja C++ spezifisch und nichts mit der WinApi zu tun.

    Bezüglich nachmessen: Das wurde von den Entwicklern der DLL bereits gemacht. Da ein spürbarer Performance Gewinn nachgewiesen werden konnte (25 - 40%) wurde diese Funktionalität erst in die DLL integriert.

    seldon schrieb:

    Ansonsten ist das Schlüsselwort "momentan." Ich würde das, wenn sich die Notwendigkeit bestätigt, im Zweifel so aufziehen, dass die relevanten Klassen statt eines HWND ein Objekt halten, das ein HWND und einen WNDPROC-Zeiger hält, der dann stumpf hin- und herkopiert werden kann. So bist du gleich gerüstet, wenn ein zweites Fenster dazukommt. Was passieren wird. So Dinge passieren immer.

    Das versteh ich jetzt nicht so ganz... Meinst du je Klasse die kommunizieren will ein statisches Kommunikations-Objekt?



  • 25-40% Gewinn durch den Funktionsaufruf? Das kann ich mir, selbst wenn SendMessage sehr dämlich implementiert wäre, höchstens dann vorstellen, wenn die Eventhandler sehr, sehr kurz sind und alle Millisekunde laufen sollen. Aber sei's drum.

    Was ich meine, ist, dass Objekte der Klassen, die mit dem Fenster sprechen wollen, statt eines Verweises auf das Fenster (hier nehme ich einen HWND an) ein Objekt halten sollten, das den Verweis auf das Fenster und einen Zeiger auf die zugehörige WndProc-Funktion halten sollten. Grob umrissen

    class direct_window {
    public:
      direct_window(HWND); // Konstruktor lädt Funktion nach
                           // Vielleicht sinnvoll: Konstruktor explicit machen, damit
                           // eine implizite Umwandlung von HWND nach direct_window
                           // nicht passiert, die wieder dynamisch Dinge laden
                           // würde.
    
      LRESULT send_message(UINT msg, LPARAM l, WPARAM w) const {
        proc_(wnd_, msg, l, w);
      }
    
      HWND get_handle() const { return wnd_; }
    
    private:
      HWND wnd_;
      WNDPROC proc_;
    };
    

    Und später:

    class talks_to_windows {
    public:
      talks_to_windows(direct_window const &wnd) // statt vorher HWND wnd
        : wnd_(wnd) { }
    
      void do_stuff() {
        wnd_.send_message(SOME_MESSAGE, 23, 42);
      }
    
    private:
      direct_window wnd_; // statt vorher HWND wnd_;
    };
    

    Dann kannst du einmal ein solches Objekt direkt erstellen und Kopien davon in allen relevanten Objekten ablegen. Auf die Art ist dein Code schon vorbereitet, wenn mal mehr Fenster dazukommen, und da eine flache Kopie hier ausreicht, wird auch nicht dauernd dynamisch Kram nachgeladen.



  • Ok, das sieht gut aus. Dann denk ich werd ich das wohl lieber so machen. Danke für die Hilfe 👍


Log in to reply