Get object's type from pointer to base class at runtime
Asked Answered
S

2

10

I'm working with a class library where all classes are, directly or indirectly, derived from a base class Base and have a name. The library provides a facility to search for objects by a name, which will return a Base*.

Is there any way to find the type of the returned object without checking all possibilities using dynamic_casts as I did in the following example? I'd like to avoid that, if at all possible, since the derived classes have template parameters, which makes for quite a few possibilities.

It would also be fine if I were at least able to find out the class type (T1 or T2, in the example below) without knowing the template type, ie. do something like a dynamic_cast<T1<i_dont_care>*>.

#include <iostream>

using namespace std;

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

template <typename T> class T1 : public Base {};
template <typename T> class T2 : public Base {};

Base *find_by_name() {
    return new T2<int>();
}

int main() {
    Base *b = find_by_name();

    if (T1<int> *p = dynamic_cast<T1<int>*>(b)) {
        cout << "T1<int>" << endl;
        // do something meaningful with p
    } else if (T1<double> *p = dynamic_cast<T1<double>*>(b))
        cout << "T1<double>" << endl;
    else if (T2<int> *p = dynamic_cast<T2<int>*>(b))
        cout << "T2<int>" << endl;
    else if (T2<double> *p = dynamic_cast<T2<double>*>(b))
        cout << "T2<double>" << endl;
    else
        cout << "unknown" << endl;

    delete b;

    return 0;
}

Note that the above example is simplified, ie. in each if I'd do something meaningful with p.

I do realize that this is bad design from the very start, however I'm stuck with this library and there's also no way for me to change its implementation.

Scalar answered 25/8, 2016 at 7:5 Comment(20)
If this functionality is desired then b should have a member for doing it. Also you could try inspecting typeid(*b).name().Longitude
your p are all redundant, write if ( dynamic_cast<T1<int>*>(b) )Longitude
Does Base really have no other members? This seems like the kind of functionality the library should provide. There is only so much you can do with a plain pointer.Monochasium
@M.M: I'll have to think about the typeid idea, thanks. Regarding the redundant ps, it seems my example was a bit too simplified :).Scalar
Return a Base*... Man, I thought we were through with the freaking Cylons.Suribachi
@juanchopanza: I thought so too, however unfortunately it doesn't...Scalar
@user4581301: had to think about it for a while, but thanks for the laugh! :)Scalar
@Suribachi all your Base* are belong to usLongitude
@Longitude typeid(*b).name() is not guaranteed to be anything in particular.Logographic
What is your desired syntax? "in each if I'd do something meaningful with p" --- like, for example, what exactly? "It would also be fine if I were at least able to find out the class type (T1 or T2, in the example below) without knowing the template type, ie. do something like a dynamic_cast<T1<i_dont_care>*>." --- this is absolutely impossible.Logographic
@n.m. In essence I'd call one function of the library if the type is T1<?> and another one if it the type is T2<?>.Scalar
Pretty much all you can do with types at runtime is comparing them - "does this object have this type?" and "does this object have the same type as that object?". You can't refer to "the dynamic type of this object" and do anything with that.Achates
What would the signature of that function look like? It cannot depend on T1 or T2. There's no way to express T1<?> in C++. You can try parsing typeid(*b).name() but this is not portable.Logographic
@n.m. To be honest, I'm still trying to figure out what's going on in the functions I'd be calling. There's a mixture of macros, templated functions, etc that I have yet to understand...Scalar
X-Y solution: Can you insert a SubBase from which your extension classes inherit? You can have one cast to find out if you're dealing with one of your classes, then call the function you want and let inheritance take care of the rest. If it's not one of your classes, well, sucks to be you.Suribachi
@Suribachi unforunately not, I can't touch the class library :(.Scalar
@n.m. I think it's guaranteed to be different for different types?Longitude
Not quite what I mean. class SubBase:public Base and SubBase declares all the helper functions you need. Your classes implement SubBase. No need for identifiers; just call the functions in SubBase, if the Base is a SubBase.Suribachi
@Longitude Nope. The literally only thing about typeid::name() in the standard is that it can be converted to wstring. No equality or inequality promise of any kind.Logographic
@Suribachi Unfortunately this is not possible since also the derived classes are provided by the library. As I said, the whole thing is a design nightmare to begin with ;).Scalar
C
6

There is a typeid operator, which returns an instance of std::type_info, with which you can get the name of the type.

Not sure if that will help you though. First, the returned name is not guaranteed to be the same across implementations. Second - what would you do once you have the name? You'd compare it with your pre-defined names probably, but that is probably slower than a bunch of dynamic_cast's.

Without type support built into your Base class or a new intermediate layer of hierarchy, dynamic_cast is your best choice. In reality it will be very fast (usually just a single compare instruction).

By intermediate layer I mean:

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

class T1Base : public Base {};
class T2Base : public Base {};

template <typename T> class T1 : public T1Base {};
template <typename T> class T2 : public T2Base {};

int main() {
    Base *b = find_by_name();

    if (dynamic_cast<T1Base*>(b))
        cout << "T1" << endl;
    else if (dynamic_cast<T2Base*>(b))
        cout << "T2" << endl;
    else
        cout << "unknown" << endl;

    delete b;

    return 0;
}
Chingchinghai answered 25/8, 2016 at 7:16 Comment(2)
Accepting this answer as it points out the best alternatives, even if it seems that none of those really help me in this particular case. A galore of dynamic_casts it is...Scalar
This is not an answer, this is a case between known class types.Sutton
C
7

There's something like typeid http://en.cppreference.com/w/cpp/language/typeid, which applied to polymorphic expression will evaluate in a runtime to its type representation.

Following wiki example: https://en.wikipedia.org/wiki/Run-time_type_information#dynamic_cast

#include <iostream> 
#include <typeinfo>    // for 'typeid'

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

class Employee : public Person {
};

int main() 
{
    Person person;
    Employee employee;
    Person* ptr = &employee;
    Person& ref = employee;
    // The string returned by typeid::name is implementation-defined

    // Person (statically known at compile-time)
    std::cout << typeid(person).name() << std::endl;   

    // Employee (statically known at compile-time)
    std::cout << typeid(employee).name() << std::endl; 

    // Person* (statically known at compile-time)
    std::cout << typeid(ptr).name() << std::endl;      

    /* Employee (looked up dynamically at run-time
     * because it is the dereference of a
     * pointer to a polymorphic class) */
    std::cout << typeid(*ptr).name() << std::endl;     

    // Employee (references can also be polymorphic)        
    std::cout << typeid(ref).name() << std::endl;      
}
Commanding answered 25/8, 2016 at 7:16 Comment(0)
C
6

There is a typeid operator, which returns an instance of std::type_info, with which you can get the name of the type.

Not sure if that will help you though. First, the returned name is not guaranteed to be the same across implementations. Second - what would you do once you have the name? You'd compare it with your pre-defined names probably, but that is probably slower than a bunch of dynamic_cast's.

Without type support built into your Base class or a new intermediate layer of hierarchy, dynamic_cast is your best choice. In reality it will be very fast (usually just a single compare instruction).

By intermediate layer I mean:

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

class T1Base : public Base {};
class T2Base : public Base {};

template <typename T> class T1 : public T1Base {};
template <typename T> class T2 : public T2Base {};

int main() {
    Base *b = find_by_name();

    if (dynamic_cast<T1Base*>(b))
        cout << "T1" << endl;
    else if (dynamic_cast<T2Base*>(b))
        cout << "T2" << endl;
    else
        cout << "unknown" << endl;

    delete b;

    return 0;
}
Chingchinghai answered 25/8, 2016 at 7:16 Comment(2)
Accepting this answer as it points out the best alternatives, even if it seems that none of those really help me in this particular case. A galore of dynamic_casts it is...Scalar
This is not an answer, this is a case between known class types.Sutton

© 2022 - 2024 — McMap. All rights reserved.