2009-07-10 12 views
7

Nous avons un sous-projet 'commonUtils' qui contient de nombreux extraits de code génériques utilisés dans le projet parent. L'une de ces choses intéressantes, j'ai vu était: -Tester si une classe est polymorphe

/********************************************************************* 
If T is polymorphic, the compiler is required to evaluate the typeid 
stuff at runtime, and answer will be true. If T is non-polymorphic, 
the compiler is required to evaluate the typeid stuff at compile time, 
whence answer will remain false 
*********************************************************************/ 
template <class T> 
bool isPolymorphic() { 
    bool answer=false; 
    typeid(answer=true,T()); 
    return answer; 
} 

Je croyais le commentaire et je pensais qu'il est tout à fait un modèle intéressant qu'il ne soit pas utilisé à travers le projet. J'ai essayé de l'employer comme ceci juste pour la curiosité ...

class PolyBase { 
public: 
    virtual ~PBase(){} 
}; 

class NPloyBase { 
public: 
    ~NBase(){} 
}; 


if (isPolymorphic<PolyBase>()) 
    std::cout<<"PBase = Polymorphic\n"; 
if (isPolymorphic<NPolyBase>()) 
    std::cout<<"NBase = Also Polymorphic\n"; 

Mais aucun de ceux jamais retourne vrai. MSVC 2005 ne donne aucun avertissement mais Comeau prévient que l'expression typée n'a aucun effet. Section 5.2.8 dans la norme C++ ne dit rien comme ce que dit le commentaire, c'est-à-dire que typeid est évalué au moment de la compilation pour les types non-polymorphes et à l'exécution pour les types polymorphes.

1) Donc je suppose que le commentaire est trompeur/plaine-faux ou puisque l'auteur de ce code est un programmeur C++ assez sénior, ai-je manqué quelque chose?

2) OTOH, je me demande si nous pouvons tester si une classe est polymorphe (a au moins une fonction virtuelle) en utilisant une technique?

3) Quand voudrait-on savoir si une classe est polymorphe? Wild devinez; pour obtenir l'adresse de début d'une classe en utilisant dynamic_cast<void*>(T) (comme dynamic_cast fonctionne uniquement sur les classes polymorphes).

En attente de vos avis.

Merci à l'avance,

+0

Er, Si l'auteur est un programmeur C++ senior, pourquoi ne pas vérifier avec lui en premier? ... Vous apprendrez souvent beaucoup de gars expérimentés. – stefanB

+9

Eh bien, si je pouvais je ne l'aurais pas demandé sur stackoverflow :-) – Abhay

Répondre

8

Je ne peux imaginer aucune façon possible comment cette typeid pourrait être utilisé pour vérifier ce type est polymorphes. Il ne peut même pas être utilisé pour affirmer que c'est le cas, car typeid fonctionnera sur n'importe quel type. Boost a une implémentation here. Quant à savoir pourquoi cela pourrait être nécessaire - un cas que je connais est la bibliothèque Boost.Serialization. Si vous enregistrez un type non polymorphe, vous pouvez simplement l'enregistrer. Si vous enregistrez un polymorphe, vous devez obtenir son type dynamique en utilisant typeid, puis appeler la méthode de sérialisation pour ce type (en le recherchant dans une table).

Mise à jour: il semble que je me trompe. Considérez cette variante:

template <class T> 
bool isPolymorphic() { 
    bool answer=false; 
    T *t = new T(); 
    typeid(answer=true,*t); 
    delete t; 
    return answer; 
} 

Cela fonctionne réellement comme le nom le suggère, exactement par commentaire dans votre extrait de code original. L'expression à l'intérieur de typeid n'est pas évaluée si elle "ne désigne pas une lvalue de type classe polymorphe" (std 3.2/2). Ainsi, dans le cas ci-dessus, si T n'est pas polymorphe, l'expression typeid n'est pas évaluée. Si T est polymorphe, alors * t est en effet lvalue de type polymorphe, donc l'expression entière doit être évaluée. Maintenant, votre exemple original est toujours faux :-). Il a utilisé T(), et non *t. Et T() créer rvalue (std 3.10/6). Donc, il donne toujours une expression qui n'est pas "lvalue de classe polymorphe".

C'est une astuce assez intéressante. D'autre part, sa valeur pratique est quelque peu limitée - parce que boost :: is_polymorphic vous donne une constante de compilation, celle-ci vous donne une valeur d'exécution, donc vous ne pouvez pas instancier un code différent pour les types polymorphes et non-polymorphes .

+0

Oui, je connais l'implémentation de boost qui utilise grossièrement la technique sizeof(). Merci pour la friandise de sérialisation. Je voulais savoir si ce modèle commonUtils est correct et deuxièmement, s'il vaut la peine de le conserver dans le projet. – Abhay

+2

Ah ah, un truc intéressant en effet, mais votre citation de la 3.10/6 était éclairante, Merci. On peut choisir sur la version modélisée lorsque la taille binaire est importante ou si l'on ne doit pas faire confiance à l'utilisateur pour fournir un dteur virtuel dans une classe polymorphe. Hélas je ne peux pas emporter 2 fois! – Abhay

3


class PolyBase { 
public: 
    virtual ~PolyBase(){} 
}; 

class NPolyBase { 
public: 
    ~NPolyBase(){} 
}; 

template<class T> 
struct IsPolymorphic 
{ 
    struct Derived : T { 
     virtual ~Derived(); 
    }; 
    enum { value = sizeof(Derived)==sizeof(T) }; 
}; 


void ff() 
{ 
    std::cout << IsPolymorphic<PolyBase >::value << std::endl; 
    std::cout << IsPolymorphic<NPolyBase>::value << std::endl; 
} 

+1

Je ne suis pas sûr que ce soit complètement infaillible. Un compilateur peut ajouter un remplissage entre les sous-objets, auquel cas sizeof() ne fonctionnerait pas. – Abhay

+1

Cela se casserait quand, disons que le Derived définit ses propres variables membres, le rendant peu pratique. – Indy9000

+0

@Indeera: Vous n'ajouteriez aucune variable membre car la structure dérivée dérive intentionnellement publiquement de la classe que vous spécifiez. Le seul inconvénient est que la classe spécifiée n'a pas de dteur virtuel mais quelques fonctions virtuelles (dans ce cas, la classe spécifiée est toujours polymorphe) en dehors du problème de remplissage. Boost suppose qu'une classe polymorphe définit un dteur virtuel et gère le remplissage en utilisant certaines spécificités du compilateur. – Abhay

-1

Je suis un peu confus ici, et j'espère obtenir quelques commentaires sur cette réponse expliquant ce qui me manque. Si vous voulez savoir si une classe est polymorphe, il vous suffit de demander si elle supporte dynamic_cast, n'est-ce pas?

template<class T, class> struct is_polymorphic_impl : false_type {}; 
template<class T> struct is_polymorphic_impl 
    <T, decltype(dynamic_cast<void*>(declval<T*>()))> : true_type {}; 

template<class T> struct is_polymorphic : 
    is_polymorphic_impl<remove_cv_t<T>, void*> {}; 

Quelqu'un peut-il signaler une faille dans cette implémentation? J'imagine qu'il doit y avoir un, ou doit avoir été un à un moment donné dans le passé, parce que the Boost documentation continue de prétendre que is_polymorphic "ne peut pas être implémenté de manière portative dans le langage C++".

Mais "portably" est une sorte de mot weasel, non? Peut-être qu'ils font juste allusion à la façon dont MSVC ne supporte pas expression-SFINAE, ou certains dialectes tels que Embedded C++ ne supportent pas dynamic_cast. Peut-être quand ils disent "le langage C++" ils signifient "un sous-ensemble du plus petit dénominateur commun du langage C++". Mais j'ai un soupçon lancinant que peut-être ils veulent dire ce qu'ils disent, et je suis celui qui manque quelque chose.

L'approche typeid dans l'OP (tel que modifié par une réponse plus tard d'utiliser un lvalue pas rvalue) semble aussi très bien, mais bien sûr, ce n'est pas constexpr et il faut construire en fait un T, ce qui pourrait être super cher. Donc, cette approche dynamic_cast semble mieux ... à moins que cela ne fonctionne pas pour une raison quelconque. Pensées?

Questions connexes