web-dev-qa-db-fra.com

Générateurs d'ID alternatifs pour les types

Dans un projet mien , j'ai un générateur ID pour les types qui ressemblent à ceci:

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{};

Usage:

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

Cela fonctionne très bien pour mes besoins, mais il a certaines limites. Le plus ennuyeux (but de la question) est qu’il échoue lorsqu’il est utilisé par un exécutable qui crée un lien vers des bibliothèques partagées si toutes essaient de créer des identificateurs. Le résultat est généralement que le nième identifiant est attribué à différents types, car chaque bibliothèque partagée conserve son propre Family::identifier séparé.

Certains {bibliothèques partagées} _ ont souligné qu'une solution plus fiable serait appréciée, mais n'ont pas suggéré de solution qui ne gâcherait pas la performance (presque tous introduisent des conteneurs, trouvent des fonctionnalités et une allocation de mémoire).

Existe-t-il une approche alternative qui contourne les limitations susmentionnées sans perdre les performances de la conception actuelle?

J'ai cherché dans SO et trouvé des réponses intéressantes. Beaucoup d'entre eux avaient plusieurs années. J'aimerais explorer des solutions jusqu'à la dernière révision de la norme, tant que l'interface de la classe existante reste intacte.
Celui-ci est le plus intéressant. Il utilise les adresses des membres statiques pour obtenir la même chose, mais ne correspond pas à l'idée d'identificateurs générés de manière séquentielle.

Note: utiliser RTTI n'est malheureusement pas une option.

Note: les identifiants doivent être générés séquentiellement et à partir de 0 comme dans la solution présentée ci-dessus.

15
skypjack

Votre problème se produit parce que vous avez cette ligne dans votre fichier d'en-tête:

std::size_t Family::identifier{};

Il finit donc dans chaque unité de traduction. À la place, vous devez déplacer le stockage pour cela dans un fichier source .cpp compilé une seule fois, éventuellement dans une bibliothèque partagée. Ensuite, un programme contiendra une seule instance de identifier et fonctionnera comme vous le souhaitez.

Vous pouvez également déplacer identifier d’une variable de classe static vers une variable globale extern dans le fichier d’en-tête (et comme ci-dessus, définissez-la dans un seul fichier .cpp).

Si vous avez C++ 17 ou version ultérieure, vous pouvez également essayer:

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

Bien que le langage ne garantisse pas (ni même ne mentionne) ce qui se passe lorsque vous utilisez cette nouvelle fonctionnalité au-delà des limites des bibliothèques partagées, cela fonctionne sur ma machine.

4
John Zwinck

si vous ne vous souciez pas des identifiants séquentiels, utilisez l'adresse de la fonction comme identifiant.

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

et alors

auto intID = getID<int>();
auto floatID = getID<float>();
...
0
Ivan Sanz-Carasa

Si vous n'avez pas besoin que les identifiants soient des entiers séquentiels, vous pouvez utiliser l'adresse d'un membre statique d'un modèle comme identifiant. L'avantage de cette approche est qu'elle ne nécessite aucune initialisation au moment de l'exécution (utilise initialization statique ):

// 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>();
}

Vous pouvez également faire de cette id une constante de temps de compilation et l'utiliser comme argument de modèle:

// 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
Maxim Egorushkin