header only singleton - über shared library grenzen - möglich?



  • Ist es in C++ möglich ein Singleton in einer Header-Only library zu erstellen,
    das auch über dynamic shared library grenzen hinaus ein singleton bleibt?

    In C++11 kann man ein singleton so implementieren:

    // instance.h
    struct instance {};
    
    instance& get_instance()
    {
        static instance obj;
        return obj;
    }
    

    Unter Windows bekommt jede DLL eine Kopie der lokalen und statischen variablen.
    Was zur folge hat, das es mehrere "singletons" gibt.

    Ist es trotzdem irgendwie möglich?



  • DLLs werden in der C++ Spezifikation nicht definiert. Du hast also kein C++ Problem

    Ob es eine Lösung gibt, weiß ich nicht. Muss es denn unbedingt Header Only sein?



  • ja es muss header only sein, das ist ja essentielle daran. Die get_instance() implementierung in die cpp schieben ist ja keine Lösung, das soll genau nicht gemacht werden.



  • Das macht so keinen Sinn. Header-Only heißt, dass der Code aus dem Header übersetzt wird, als stände er in der .cpp-Datei.
    D.h. ob die Variable in der DLL oder der EXE steht, hängt davon ab, wo du den Header einbindest.

    Wenn du die globale Variable in die Unit der DLL packst, hat auch nur die DLL eine einmalige Kopie davon (sofern die DLL nicht mehrfach geladen wird - geht das?)



  • someone_ schrieb:

    ja es muss header only sein, das ist ja essentielle daran.

    Was ist der Grund dafür?
    Weil du einfach wissen willst ob genau das möglich ist, oder willst du etwas bestimmtes damit erreichen?
    Falls letzteres, dann gibt es vielleicht ausweichsmöglichkeiten.

    Wenn es dir darum geht dass der Zugriff möglichst schnell sein soll, dann ginge z.B. folgendes:

    // instance.h
    struct instance {};
    
    __declspec(dllimport) instance* real_get_instance();
    
    inline instance& get_instance()
    {
        static instance* obj = real_get_instance();
        return *obj;
    }
    

    Wobei ich mir nicht sicher bin, ob aktuelle Compiler überhaupt Funktionen inlinen können, welche dynamisch initialisierte statische Variablen enthalten. Falls nicht, könnte man nochmal 'was rausholen indem man das Erzeugen des Objekts beim 1. Durchlauf selbst implementiert. Und damit inlining wieder möglich macht.



  • hustbaer schrieb:

    someone_ schrieb:

    ja es muss header only sein, das ist ja essentielle daran.

    Was ist der Grund dafür?
    Weil du einfach wissen willst ob genau das möglich ist, oder willst du etwas bestimmtes damit erreichen?
    Falls letzteres, dann gibt es vielleicht ausweichsmöglichkeiten.

    Wenn es dir darum geht dass der Zugriff möglichst schnell sein soll, dann ginge z.B. folgendes:

    // instance.h
    struct instance {};
     
    __declspec(dllimport) instance* real_get_instance();
    
    inline instance& get_instance()
    {
        static instance* obj = real_get_instance();
        return *obj;
    }
    

    Wobei ich mir nicht sicher bin, ob aktuelle Compiler überhaupt Funktionen inlinen können, welche dynamisch initialisierte statische Variablen enthalten. Falls nicht, könnte man nochmal 'was rausholen indem man das Erzeugen des Objekts beim 1. Durchlauf selbst implementiert. Und damit inlining wieder möglich macht.

    Du willst wirklich wissen was der Grund für eine header only library ist, anstatt eine, ja wie nennt man es, Modul zu erzeugen?

    Also um eine Header-Only library zu werwenden, muss ich nur ein #include machen.
    Für ein C++ Modul muss ich die Bibliothek anlinken + die Header inkludieren. Natürlich auch x86/x64 berücksichtigen usw. usf.

    Mir geht es nicht um Performance.



  • someone_ schrieb:

    Du willst wirklich wissen was der Grund für eine header only library ist, anstatt eine, ja wie nennt man es, Modul zu erzeugen?

    Ne, ich weiss schon was die Vorteile von Header-Only Libraries sind.
    Was ich nicht weiss, ist warum du eine machen willst. Bzw. war ich mir nicht sicher ob es dir nicht einfach nur um Performance geht.

    ----

    Also wenn es wirklich 100% Header-Only sein soll, dann werden statische/globale Daten ... schwierig. Weil ich keinen Compiler kenne der das von sich aus irgendwie unterstützt.

    Ich habe vor einiger Zeit mal über ein ähnliches Problem nachgedacht. Und die Sache ist alles andere als trivial. Es gibt dabei zwei Schwierigkeiten.

    1. Man benötigt irgend eine (OS-abhängige, weil der Standard nix bietet) Funktion, bzw. allgemeiner einen "Mechanismus", mittels der/dem man Daten in einem Prozess mit einem "Key" hinterlegen kann. Und wer den selben "Key" verwendet, greift auf die selben Daten zu. (Und "Daten" heisst in dem Fall einfach: einen Zeiger auf dein Singleton bzw. eine Singleton-Registry falls es mehr als 1 Singleton gibt.)

    2. Das Problem mit den VTables. Angenommen dein Singleton hat virtuelle Funktionen, oder enthält Objekte die wiederrum virtuelle Funktionen haben, oder erzeugt solche Objekte. Dann muss sichergestellt sein dass zu jedem Zeitpunkt wo auf diese Objekte zugegriffen wird der VTable dieser Objekte noch da ist, sowie natürlich alle Funktionen die über diesen VTable erreichbar sind.

    (1) kann man unter Windows z.B. durch "named shared memory" lösen. Beispiel dazu: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366551(v=vs.85).aspx
    Als Name sollte man etwas "ausreichend eindeutiges" verwenden, z.B. nen kurzen verständlichen Prefix + Hex-Darstellung einer dafür generierten GUID. Zusätzlich sollte die Prozess-ID mit rein, denn man will na nicht wirklich "shared memory" - man verwendet den Mechanismus ja bloss als Mittel zum Zweck.
    Wie es auf anderen OSen geht weiss ich nicht. Du könntest aber 'mal die Boost.Interprocess checken - vielleicht gibt es da auch die Möglichkeit die "shared memory" Objekte mit (dynamisch generierten) Namen zu versehen.

    (2) ist etwas allgemeiner. Das selbe Problem hat man ja auch bei Dingen wie Plugin DLLs etc. Je nachdem was das deine Library alles macht, und was das Programm das deine Library verwendet alles macht, kann es sein dass du überhaupt kein Problem hast -- oder ein unlösbares. Denn die einzig mir bekannte Möglichkeit da überhaupt etwas zu machen, falls es nicht sowieso eine "non-issue" ist, ist DLLs zu pinnen. Also dem OS zu verbieten sie jemals wieder zu entladen. Und je nach Applikation kann das ein no-go sein.

    ps:
    Es gibt auch zwischen "header only" und "klassisch" noch eine Möglichkeit. Nämlich einfach ein Header File + .cpp File auszuliefern. Der ganze Code darin ist genau so wie bei einer "header only" Library so geschrieben dass er ohne Konfiguration auf allen unterstützten Plattformen "einfach so" geht. Der Client muss dann zusätzlich zum "#include Schreiben" bloss noch ein einziges .cpp File in sein Projekt ziehen. Oder alternativ in einem .cpp File seines Programms ein "#include <Foo/Foo.cpp>" schreiben.
    Was Singletons angeht ergeben sich dadurch natürlich gewisse Einschränkungen. z.B. kann man es dann nicht unter Windows verwenden wenn Code aus verschiedenen PE Files diese Library gemeinsam verwenden soll. (Unter Linux sollte es mMn. gehen. So genau kenne ich mich allerdings auch nicht mit den Eigenheiten der Behandlung von Shared Objects aus dass ich meine Hand dafür ins Feuer legen würde.)

    ps2:
    RTTI könnte theoretisch noch zu einem Problem werden. Praktisch gibt es denke ich keines. Mit MSVC geht's auf jeden Fall. GCC und Clang solltest du nochmal checken.



  • someone_ schrieb:

    Du willst wirklich wissen was der Grund für eine header only library ist, anstatt eine, ja wie nennt man es, Modul zu erzeugen?

    Yup. Die Dinger haben nämlich auch Nachteile - den gleichen Nachteil, den alle Anwendungen haben, wenn man sie statisch linkt.

    Stell dir vor, deine Lib wird von 50 verschiedenen Programmen verwendet, dynamisch gelinkt. Dann stellst du fest, dass da ein fetter Fehler drin ist. Du fixt den Fehler, kompilierst die Bibliothek neu, und schon sind alle Programme gefixt (nicht die laufenden, die musst du neustarten).

    Jetzt nimm dir eine Header-Only oder eine statisch gelinkte Bibliothek: du entdeckst einen Fehler, fixt diesen, und musst jetzt jedes Programm neu linken (oder eventuell sogar neu kompilieren), damit der Fehler gefixt wird. Und das für alle 50 Programme.

    Also: was ist die Gründe für eine Header-Only-Lib? Komfort? Habe ich gerade ausgeräumt. Performance (statisch gelinkt ist geringfügig schneller als dynamisch, aber nicht viel, und auch nur beim Starten der Anwendung)? Wenn du Echtzeitsysteme am Laufen hast, die sofort auf der Matte stehen müssen, und wo nichts geswappt werden darf - oder wenn du auf einem Embedded-System programmierst, dann können die Nachteile überwiegen. Aber sonst willst du eigentlich immer dynamisch linken.

    EDIT: War nicht zu Ende formatiert.



  • Achja...

    someone_ schrieb:

    Für ein C++ Modul muss ich die Bibliothek anlinken + die Header inkludieren. Natürlich auch x86/x64 berücksichtigen usw. usf.

    Bei einem Compiler der Autolink kann ( #pragma comment(lib) oder Ähnliches), musst du auch bloss #include schreiben.
    (Und natürlich dafür sorgen dass das .lib File dort liegt wo es der Linker finden kann, und dass das .DLL/.so File mit ausgeliefert wird.)
    Also SO schlimm ist das auch nicht.

    Trotzdem verstehe ich den Wunsch nach Header-only. Ist für den User halt wirklich die angenehmste Variante.



  • Darum freuen wir uns auf kommende Standards, die bringen dann Module mit sich. Vielleicht.



  • Was vereinfacht sich da (Module) bezüglich Linker-Einstellungen?


Anmelden zum Antworten