Recherche du type d'un objet en C ++

147

J'ai une classe A et une autre classe qui en hérite, B.Je substitue une fonction qui accepte un objet de type A comme paramètre, donc je dois accepter un A. Cependant, j'appelle plus tard des fonctions que seul B a, donc je veux retourner false et ne pas continuer si l'objet passé n'est pas de type B.

Quelle est la meilleure façon de savoir de quel type est l'objet passé à ma fonction?

lemnisca
la source

Réponses:

162

dynamic_cast devrait faire l'affaire

TYPE& dynamic_cast<TYPE&> (object);
TYPE* dynamic_cast<TYPE*> (object);

Le dynamic_castmot clé convertit une donnée d'un pointeur ou d'un type de référence vers un autre, en effectuant une vérification à l'exécution pour garantir la validité de la conversion.

Si vous essayez de convertir en pointeur vers un type qui n'est pas un type d'objet réel, le résultat de la conversion sera NULL. Si vous essayez de convertir en référence à un type qui n'est pas un type d'objet réel, le cast lèvera une bad_castexception.

Assurez-vous qu'il existe au moins une fonction virtuelle dans la classe de base pour que dynamic_cast fonctionne.

Rubrique Wikipédia Informations sur le type d'exécution

RTTI n'est disponible que pour les classes polymorphes, ce qui signifie qu'elles ont au moins une méthode virtuelle. En pratique, ce n'est pas une limitation car les classes de base doivent avoir un destructeur virtuel pour permettre aux objets des classes dérivées d'effectuer un nettoyage approprié s'ils sont supprimés d'un pointeur de base.

yesraaj
la source
1
Que voulez-vous dire par il doit y avoir une fonction virtuelle dans la classe de base pour faire fonctionner dynamic_cast. Cela me semble très important, je vais juste deviner.
GiCo
3
OK trouvé: les informations de type à l'exécution (RTTI) ne sont disponibles que pour les classes polymorphes, ce qui signifie qu'elles ont au moins une méthode virtuelle. dynamic_cast et typeid nécessitent RTTI.
GiCo
Ne dynamic_castjette pas si ce n'est pas convertible? Y a-t-il un moyen de le faire sans générer de lancer?
jww
A* aptr = dynamic_cast<A*>(ptr);// n'est pas censé être comme ça
Mehdi Karamosly
Cela fonctionne-t-il pour les POD qui ont été convertis en un uint8_t*? Autrement dit, puis-je vérifier cela uint32_t* x = dynamic_cast<uint32_t*>(p), où pest-il uint8_t*? (J'essaie de trouver un test pour punir les violations).
jww
157

La distribution dynamique est la meilleure pour votre description du problème, mais je veux juste ajouter que vous pouvez trouver le type de classe avec:

#include <typeinfo>

...
string s = typeid(YourClass).name()
Robocide
la source
4
Bien si vous ne savez vraiment pas quel est votre objet. La réponse acceptée suppose que vous le faites.
unludo
4
@xus Oui. il fait partie des en
Amey Jah
8
Je ne vois pas comment. Les noms d'identifiant de type ne sont pas tenus d'être utiles et sont définis par l'implémentation.
Chaussure
3
Le plus intéressant ici: les noms des instances de la même classe ne doivent pas nécessairement être égaux. Cependant, le typeid lui-même doit comparer égal pour les instances de la même classe, voir stackoverflow.com/questions/1986418/typeid-versus-typeof-in-c
FourtyTwo
1
Remarque gcc renvoie le nom magled par exemple 11MyClass. Pour démêler, vous pouvez utiliser la bibliothèque d'extension ABI dans cxxabi.h. Cela vous donne abi::__cxa_demanglequi vous donnera le vrai nom
David G
27

Cela s'appelle RTTI , mais vous voulez presque sûrement reconsidérer votre conception ici, car trouver le type et faire des choses spéciales en fonction de celui-ci rend votre code plus fragile.

Ana Betts
la source
4
Vrai. Malheureusement, je travaille sur un projet existant, donc je ne peux pas vraiment changer le design, ou quoi que ce soit de la classe A.
lemnisca
11

Juste pour être complet, je vais construire une version de Robocide et souligner qu'il typeidpeut être utilisé seul sans utiliser name ():

#include <typeinfo>
#include <iostream>

using namespace std;

class A {
public:
    virtual ~A() = default; // We're not polymorphic unless we
                            // have a virtual function.
};
class B : public A { } ;
class C : public A { } ;

int
main(int argc, char* argv[])
{
    B b;
    A& a = b;

    cout << "a is B: " << boolalpha << (typeid(a) == typeid(B)) << endl;
    cout << "a is C: " << boolalpha << (typeid(a) == typeid(C)) << endl;
    cout << "b is B: " << boolalpha << (typeid(b) == typeid(B)) << endl;
    cout << "b is A: " << boolalpha << (typeid(b) == typeid(A)) << endl;
    cout << "b is C: " << boolalpha << (typeid(b) == typeid(C)) << endl;
}

Production:

a is B: true
a is C: false
b is B: true
b is A: false
b is C: false
firebush
la source
9

Incorporez probablement dans vos objets une "balise" d'identification et utilisez-la pour faire la distinction entre les objets de classe A et les objets de classe B.

Cela montre cependant un défaut dans la conception. Idéalement, les méthodes de B que A n'a pas, devraient faire partie de A mais laissées vides, et B les écrase. Cela supprime le code spécifique à la classe et s'inscrit davantage dans l'esprit de la POO.

espace libre
la source
8

Tu recherches dynamic_cast<B*>(pointer)

Joshua
la source
4

Parce que votre classe n'est pas polymorphe. Essayer:

struct BaseClas { int base; virtual ~BaseClas(){} };
class Derived1 : public BaseClas { int derived1; };

Maintenant BaseClasest polymorphe. J'ai changé de classe en struct parce que les membres d'une structure sont publics par défaut.

c64zottel
la source
3

Votre description est un peu déroutante.

De manière générale, bien que certaines implémentations C ++ aient des mécanismes pour cela, vous n'êtes pas censé poser des questions sur le type. Au lieu de cela, vous êtes censé faire un dynamic_cast sur le pointeur vers A. Ce que cela fera, c'est qu'au moment de l'exécution, le contenu réel du pointeur vers A sera vérifié. Si vous avez un B, vous obtiendrez votre pointeur vers B. Sinon, vous obtiendrez une exception ou null.

Uri
la source
1
Il convient de noter que vous n'obtiendrez une exception que si vous effectuez un cast de référence qui échoue. c'est-à-dire dynamic_cast <T &> (t). Échec de la casse du pointeur renvoie NULL. ie dynamic_cast <T *> (t)
AlfaZulu
Oui, j'aurais dû mieux clarifier cela. Merci. J'aurais aimé qu'il y ait un mot pour décrire les types C qui sont par référence plutôt que par valeur.
Uri
3

Comme d'autres l'ont indiqué, vous pouvez utiliser dynamic_cast. Mais généralement utiliser dynamic_cast pour découvrir le type de la classe dérivée sur laquelle vous travaillez indique une mauvaise conception. Si vous surchargez une fonction qui prend le pointeur de A comme paramètre, elle devrait pouvoir fonctionner avec les méthodes / données de la classe A elle-même et ne devrait pas dépendre des données de la classe B.Dans votre cas, au lieu de remplacer si vous êtes sûr que la méthode que vous écrivez fonctionnera uniquement avec la classe B, alors vous devez écrire une nouvelle méthode dans la classe B.

Naveen
la source
1

Utilisez des fonctions surchargées. Ne nécessite pas de dynamic_cast ni même de support RTTI:

class A {};
class B : public A {};

class Foo {
public:
    void Bar(A& a) {
        // do something
    }
    void Bar(B& b) {
        Bar(static_cast<A&>(b));
        // do B specific stuff
    }
};
jmucchiello
la source
Droit de la question originale: "J'appelle plus tard des fonctions que seul B a" - comment la surcharge fonctionnerait dans un tel cas?
Marcin Gil
Lorsque vous appelez Bar avec un A, rien de B ne se produit. Lorsque vous appelez Bar avec un B, les méthodes qui n'existent que sur B peuvent être appelées. Avez-vous lu la question originale? Bar est son "Je remplace une fonction qui accepte un objet de type A comme paramètre"
jmucchiello
7
Cela ne fonctionne pas avec le polymorphisme dynamique, que je soupçonne que le questionneur utilise. C ++ ne peut pas sélectionner une surcharge en fonction de la classe d'exécution du paramètre, uniquement en fonction du type à la compilation.
Steve Jessop
1

Si vous pouvez accéder à la bibliothèque boost, peut-être que la fonction type_id_with_cvr () est ce dont vous avez besoin, qui peut fournir un type de données sans supprimer les modificateurs const, volatile et && . Voici un exemple simple en C ++ 11:

#include <iostream>
#include <boost/type_index.hpp>

int a;
int& ff() 
{
    return a;
}

int main() {
    ff() = 10;
    using boost::typeindex::type_id_with_cvr;
    std::cout << type_id_with_cvr<int&>().pretty_name() << std::endl;
    std::cout << type_id_with_cvr<decltype(ff())>().pretty_name() << std::endl;
    std::cout << typeid(ff()).name() << std::endl;
}

J'espère que c'est utile.

Kehe CAI
la source