8 votes

Comment détecter si une classe a des variables membres ?

Problème

Je voudrais détecter si une classe a des variables membres et échouer un static assert si c'est le cas. Quelque chose comme :

struct b {
    int a;
}
static_assert(!has_member_variables, "La classe ne doit pas contenir de membres"). // Erreur.

struct c {
    virtual void a() {}
    void other() {}
}
static_assert(!has_member_variables, "La classe ne doit pas contenir de membres"). // Bien.

struct d : c {
}
static_assert(!has_member_variables, "La classe ne doit pas contenir de membres"). // Bien.

struct e : b {
}
static_assert(!has_member_variables, "La classe ne doit pas contenir de membres"). // Erreur.

struct f : c {
    char z;
}
static_assert(!has_member_variables, "La classe ne doit pas contenir de membres"). // Erreur.

**

Est-il possible d'atteindre cet objectif avec un modèle SFINAE ? Cette classe peut avoir de l'héritage voire même de l'héritage multiple avec des fonctions virtuelles (sans membres dans les classes de base cependant).

Motivation

J'ai une configuration assez simple comme suit :

class iFuncRtn {
    virtual Status runFunc(Data &data) = 0;
};

template 
class FuncRoutineDataHelper : public iFuncRtn {
    Status runFunc(Data &data) {
        static_assert(!has_member_variables, "Les routines ne doivent pas avoir de membres de données !");
        // Préparer des données spéciales pour la routine
        TSpecialDataType sData(data);
        runFuncImpl(sData);
}

class SpecificRtn : 
    public FuncRoutineDataHelper {
    virtual Status runFuncImpl(MySpecialData &sData) {
        // Calcul basé sur l'entrée 
        sData.setValue(someCalculation);
    }
};

Les Routines de fonctionnalité sont gérées et exécutées à chaque tick. Elles sont personnalisées et peuvent effectuer une grande variété de tâches comme contacter d'autres appareils, etc. Les données qui sont passées peuvent être manipulées par la routine et sont garanties d'être passées à chaque exécution de tick jusqu'à ce que la fonctionnalité soit terminée. Le bon type de données est passé en fonction du DataHelper class. Je veux dissuader les futures personnes de rajouter accidentellement des données aux routines de fonctionnalité car il est très peu probable que cela fasse ce qu'ils attendent. Pour forcer cela, j'espérais trouver un moyen avec un static assert.

**

9voto

joe_chip Points 2084

Vous pouvez résoudre cela en vous appuyant sur l'optimisation de la classe de base vide effectuée par le compilateur, en vérifiant si une classe dérivée de votre T a la même taille qu'une classe vide avec des fonctions virtuelles :

template
class IsEmpty
{
    // vérification de cohérence ; voir la démo mise à jour ci-dessous
    static_assert(IsDerivedFrom::value);

    struct NonDerived : BaseClasses... { virtual ~NonDerived() = default; };
    struct Derived : T { virtual ~Derived() = default; };

public:
    inline static constexpr bool value = (sizeof(NonDerived) == sizeof(Derived));
};

Cela devrait fonctionner avec l'héritage simple et multiple. Cependant, lors de l'utilisation de l'héritage multiple, il est nécessaire de lister toutes les classes de base, comme ceci :

static_assert(IsEmpty::value);

Évidemment, cette solution exclut les classes final.

Voici la démo mise à jour.

Voici la démo originale. (ne fonctionne pas avec l'héritage multiple)

4voto

Celess Points 519

Vous devrez marquer les classes d'une manière ou d'une autre. Choisissez une méthode avec laquelle vous êtes à l'aise, une propriété ou un membre de type entier avec une énumération. Celui qui crée des sous-classes devra suivre votre convention pour que cela fonctionne.

Toutes les autres réponses ici seront des variantes de cela.

Toute réponse utilisant un sizeof ne peut pas garantir que cela fonctionnera entre les plates-formes, les compilateurs, ou même les classes sur la même plate-forme et le même compilateur, en raison de la possibilité d'ajouter facilement un nouveau membre à l'intérieur de l'alignement par défaut des membres de la classe, où les tailles de sizeof pourraient facilement être les mêmes pour une sous-classe.


Contexte:

Comme indiqué dans votre code et votre question, tout cela n'est que du code C et C++ simple et basique, et est résolu entièrement lors de la compilation. Le compilateur vous dira si un membre existe ou non. Une fois compilé, c'est un agglomérat de code machine efficace, anonyme, sans indices ni aide pour ce genre de chose en soi.

N'importe quel nom que vous utilisez pour une fonction ou un membre de données disparaît effectivement, car vous le connaissez et le voyez là, après la compilation, et il n'y a aucun moyen de rechercher un membre par nom. Chaque membre de données est connu uniquement par son décalage numérique depuis le sommet de la classe ou de la structure.

Des systèmes comme .Net, Java, et d'autres sont conçus pour la réflexion, qui est la capacité de se souvenir des membres de la classe par nom, où vous pouvez les trouver à l'exécution lorsque votre programme est en cours d'exécution.

Les modèles en C++, à moins d'être en mode mixte C++ sur quelque chose comme .Net, sont également tous résolus lors de la compilation, et les noms seront également tous disparus, donc les modèles en soi ne vous apportent rien.

Des langages comme Objective-C sont également écrits de manière à ne pas échouer nécessairement si certains types de membres spéciaux manquent, similaire à ce que vous demandez, mais en dessous il utilise beaucoup de code de support et de gestion à l'exécution pour garder une trace indépendamment, où la fonction réelle et son code sont toujours inconscients et dépendent d'un autre code pour leur dire si un membre existe ou ne pas échouer en cas de membre nul.


En C ou C++ pur, il faut simplement créer son propre système, et être précis sur le suivi dynamique de ce qui fait quoi. Vous pourriez créer des énumérations, des listes ou des dictionnaires de chaînes de caractères. C'est ce qui est habituellement fait, vous devez juste laisser des indices pour vous-même. Une classe ne peut pas être compilée de manière à donner une visibilité implicite aux futures sous-classes par définition, sans utiliser une forme de RTTI.

Il est courant de mettre un membre de type sur une classe pour cette raison précise, qui pourrait être une simple énumération. Je ne compterais pas sur des tailles ou quoi que ce soit qui pourrait dépendre de la plate-forme.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X