Home > Enterprise >  Check intermediate ancestor type of object
Check intermediate ancestor type of object

Time:09-29

Some places in our code make extensive use of 'dynamic_cast` do check the type of a given object:

    if (dynamic_cast<Foo*>(bar))
        return "foo";
    else
        return "not-foo";

In some specific section of the code, we decided to switch to typeid, but we ran into a problem: we're checking an object against an arbitrary ancestor, not to its concrete type:

#include <typeinfo>
#include <iostream>

struct Base
{
    virtual ~Base() {} // enable vtable
};

struct Derived : Base{};

struct DerivedAgain : Derived{};
struct OtherDerived : Derived{};

int main() {
    Base* b = new DerivedAgain;
    // if (typeid(*b) == typeid(Derived)) // will print false
    if (dynamic_cast<Derived*>(b))
        std::cout << "true\n";
    else
        std::cout << "false\n";

    return 0;
}

Is there a way to check if b is Derived* without dynamic_cast?

P.S.: I'm aware this might be indicative of some larger problem with the design of the code, but I want to know specifically how to make this kind of check.

CodePudding user response:

A quick way to go around dynamic_cast, with some manual work, if you have relatively few types:

  • add a static constexpr/const int mytypeno member to each class with a distinct prime number (this can be done automatically)
  • add another static constexpr/const int mytypecompatibility member, which is:
    • for base (topmost classes) the same as mytypeno
    • for classes that has base classes, the product of their mytypeno and all base classes' mytypeno

Then you can check if a class is a descendant of another as: descendant.mytypecompatibility % base.mytypeno == 0. This idea was originally proposed by Bjarne for a quick implementation of dynamic_cast<> with full access to all sources (vs. local solutions). It's also relatively little intrusion as you only take two static constexpr members, thus no per-object overhead and you don't force virtual table to be used.

CodePudding user response:

It seems that dynamic_cast is what I want in this specific case, since it is the only thing in the language that will actually examine the dependency chain of the object.


That said, it's worth mentioning that, as I said in the question and many people said in the comments, having to use dynamic_cast may be an indicative of a larger design problem in the code. One possible solution is using a polymorphic method instead:

// with checking
int doThing(Base* b)
{
    if (auto d = dynamic_cast<Derived*>(b))
        return stuff(d);
    if (auto d = dynamic_cast<OtherDerived*>(b))
        return otherStuff(d);

    return 0;
}

// with polymorphism
int doThing(Base* b)
{
    return b->stuff();
}

// stuff is a virtual method defined in Base, Derived and OtherDerived
struct Base
{
    virtual int stuff() const {}
};

struct Derived : Base
{
    int stuff() const override { return stuff(this); }
};

struct OtherDerived : Base
{
    int stuff() const override { return otherStuff(this); }
};

  • Related