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
- for base (topmost classes) the same as
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); }
};