Search code examples
c++polymorphismtypeinfo

Discerning between polymorphic types in C++


Here is some code:

#include <typeinfo>
#include <assert.h>
#include <vector>

class Super {};

class Sub1 : public Super {};

class Sub2 : public Super {};

int main() {
    std::vector<Super*> vec;

    vec.push_back(new Sub1);
    vec.push_back(new Sub2);

    assert(typeid(vec[0]) == typeid(vec[1]));
    assert(typeid(vec[0]) != typeid(vec[1]));
}

Unfortunately the first assert passes, and the second does not. I am not surprised by that result, but it would have been nice to be able to discern types that way.

My (sort of hackish) workaround:

#include <typeinfo>
#include <assert.h>
#include <vector>

enum SubTypeEnum {
    Sub1_T,
    Sub2_T
};

class Super {
    SubTypeEnum _type;
public:
    Super(SubTypeEnum __type) : _type(__type) {}
    SubTypeEnum type() { return _type; }
};

class Sub1 : public Super {
public:
    Sub1() : Super(Sub1_T) {}
};

class Sub2 : public Super {
public:
    Sub2() : Super(Sub2_T) {}
};

int main() {
    std::vector<Super*> vec;

    vec.push_back(new Sub1);
    vec.push_back(new Sub2);

    assert(vec[0]->type() != vec[1]->type());
    assert(vec[0]->type() == vec[1]->type());
}

This produces the desired result, but seems messy. Is there a better way to find out which type I am dealing with?


Solution

  • Firstly you're using typeinfo slightly wrongly, when applied to a pointer it returns the type of the pointer, but when applied to a dereferenced pointer it returns the actual type of the object pointed to, so long as the base type has at least one virtual function ( usually it would be the destructor). So the following will work:

    class Super {
      public:
      virtual ~Super() {}
    };
    
    class Sub1 : public Super {};
    
    class Sub2 : public Super {};
    
    int main() {
        std::vector<Super*> vec;
    
        vec.push_back(new Sub1);
        vec.push_back(new Sub2);
    
        assert(typeid(vec[0])  == typeid(vec[1]));
        assert(typeid(*vec[0]) != typeid(*vec[1]));
    }
    

    Secondly, doing this kind of type switching is generally considered a sign that you're doing something wrong. For example instead of

    void foo(Base* v) {
      //Here SOMETHING and SOMETHING_ELSE are constants we calculate elsewhere.
      if( typeid(*v)==SOMETHING ) { cout<<"Got a SOMETHING"<<endl; }
      else if (typeid(*v)==SOMETHING_ELSE ) { cout<<"Got a SOMETHING ELSE"<<endl; }
    }
    

    or its roughly equivalent

    void foo(Base* v) {
      if( dynamic_cast<Something*>(v) ) { cout<<"Got a SOMETHING"<<:endl; }
      else if ( dynamic_cast<SomethingElse*>(v) { cout<<"Got a SOMETHING ESLE"<<endl; }
    

    it is usual to add the functionality into the base class:

    class Base
    {
      public:
        virtual void printMessage()=0;
    };
    
    class Something : public Base
    {
       void printMessage() { cout<<"Got a SOMETHING"<<endl; }
    }
    
    class SomethingElse : public Base
    {
       void printMessage() { cout<<"Got a SOMETHING ELSE"<<endl; }
    }
    
    void foo(Base * v)
    {
      v->printMessage();
    }