web-dev-qa-db-de.com

Alternative ID-Generatoren für Typen

In ein Projekt von mir habe ich einen ID-Generator für Typen, der ähnlich aussieht:

class Family {
    static std::size_t identifier;

    template<typename...>
    static std::size_t family() {
        static const std::size_t value = identifier++;
        return value;
    }

public:
    template<typename... Type>
    inline static std::size_t type() {
        return family<std::decay_t<Type>...>();
    }
};

std::size_t Family::identifier{};

Verwendungszweck:

const auto id = Family::type<FooBar>();

Es funktioniert gut für meine Zwecke, hat aber einige Einschränkungen. Das Ärgerlichste (Zweck der Frage) ist, dass die Anwendung fehlschlägt, wenn sie von einer ausführbaren Datei verwendet wird, die mit freigegebenen Bibliotheken verknüpft ist, wenn alle versuchen, Bezeichner zu erstellen. Das Ergebnis ist normalerweise, dass der n-te Bezeichner unterschiedlichen Typen über Grenzen hinweg zugewiesen wird, da jede gemeinsam genutzte Bibliothek ihren eigenen Family::identifier verwaltet.

Einige shared library guys wiesen darauf hin, dass eine zuverlässigere Lösung wünschenswert wäre, schlug jedoch keine vor, die die Leistung nicht beeinträchtigen würde (fast alle führen Container ein, finden Funktionen und Speicherzuordnung).

Gibt es einen alternativen Ansatz, der die oben genannten Einschränkungen umgeht, ohne die Leistung des aktuellen Designs zu verlieren?

Ich durchsuchte SO und fand einige interessante Antworten. Viele davon waren mehrere Jahre alt. Ich möchte stattdessen Lösungen bis zur neuesten Version des Standards erkunden, solange die Schnittstelle der vorhandenen Klasse intakt bleibt.
Dieser ist der interessanteste. Es verwendet Adressen von statischen Elementen, um dasselbe zu erreichen, aber es passt nicht zu der Idee von sequentiell erzeugten Bezeichnern

Note: Die Verwendung von RTTI ist leider nicht möglich.

Note: IDs müssen wie in der oben dargestellten Lösung sequenziell und von 0 beginnend generiert werden.

15
skypjack

Ihr Problem tritt auf, weil Sie diese Zeile in Ihrer Headerdatei haben:

std::size_t Family::identifier{};

Es endet daher in jeder Übersetzungseinheit. Stattdessen müssen Sie den Speicher dafür in eine .cpp-Quelldatei verschieben, die nur einmal kompiliert wird, möglicherweise in eine eigene gemeinsam genutzte Bibliothek. Dann gibt es nur eine Instanz von identifier in einem Programm, und es funktioniert wie gewünscht.

Sie können auch identifier von einer Variablen der Klasse static in eine globale Variable extern in der Headerdatei verschieben (und wie oben definiert, definieren Sie sie in einer einzelnen .cpp-Datei).

Wenn Sie über C++ 17 oder höher verfügen, können Sie auch Folgendes versuchen:

inline std::size_t Family::identifier{};

Obwohl die Sprache nicht garantiert (oder gar nicht erwähnt) wird, was passiert, wenn Sie diese neue Funktion über die Grenzen gemeinsamer Bibliotheken hinweg verwenden, funktioniert sie auf meinem Computer.

4
John Zwinck

wenn Sie sich nicht für sequentielle IDs interessieren, verwenden Sie die Adresse der Funktion als Bezeichner.

template<typename... T>
uintptr_t getID() {
    return reinterpret_cast<uintptr_t>(&getID<T...>);
}

und dann

auto intID = getID<int>();
auto floatID = getID<float>();
...
0

Wenn es sich bei IDs nicht um sequenzielle Ganzzahlen handelt, können Sie die Adresse eines statischen Mitglieds einer Vorlage als ID verwenden. Der Vorteil dieses Ansatzes ist, dass keine Laufzeitinitialisierung erforderlich ist (verwendet statische Initialisierung ):

// in a header
class Family {
    template<class...> struct Id { static char const id; };

    template<typename... T>
    static std::size_t family() {
        return reinterpret_cast<std::size_t>(&Id<T...>::id);
    }

public:
    template<typename... Type>
    static std::size_t type() {
        return family<std::decay_t<Type>...>();
    }
};

// in a header
template<class... T>
char const Family::Id<T...>::id = {};

// elsewhere    
int main() {
    auto int_id = Family::type<int>();
    auto int_int_id = Family::type<int, int>();
}

Sie können diese id auch zu einer Kompilierungszeitkonstante machen und als Vorlagenargument verwenden:

// in a header
struct Family {
    template<class...> struct Id { static char const id; };
};

// in a header
template<class... T>
char const Family::Id<T...>::id = {};

// elsewhere    
template<char const*>
struct X {};

int main() {
    X<&Family::Id<int>::id> x;
}
0