4

I have a class structure with loads of different child classes all inheriting from the same abstract base class. This base class is there because all these classes self register at compile time to a factory from which they are built. This is really neat and keeps me from the burden of maintaining a huge switch or any other flow mechanism somewhere down stream.

              +---------------+
              |     Base      |
              +--+---------+--+
                 |         |
         +-------+-+     +-+-------+
         |  Kid1   |     |  Kid2   |
         +----+----+     +----+----+
              |               |
  +---+---+---+               +---+---+---+
  |   |   |   | ...           |   |   |   | ....

However, these Kid1 and Kid2 classes are different and the issue is that I have to distinguish between them somewhere in my code and the only thing I have is a Base pointer. I did not want to bother the factory with this and keep that as simple as possible.

The way I solved this right now is by having Base being somewhat aware of the two siblings. It has a virtual method type() which returns an enum type distinguishing between Kid1 and Kid2. Both kids override this (basically saying I am KidX) such that I know with whom I am dealing. This type is then used to dynamic_cast Base to either of the kids and the program proceeds.

However, is this the right approach?

I could for example add some more virtual methods to base, on the one hand polluting it with methods only one part of the hierarchy uses but on the other hand saving me the dynamic_cast.

Any thoughts?

Guillaume Jacquenot
  • 11,217
  • 6
  • 43
  • 49
Montaldo
  • 863
  • 8
  • 16
  • An alternative to your `type()` member would be to just attempt a dynamic_cast to Kid1, then Kid2 ... KidN and see which one succeeds. Not pretty. Simply pointing out that it *is* an alternative. – Jesper Juhl May 18 '16 at 18:48
  • @JesperJuhl I toyed with that idea as well but cant ignore the uglyness – Montaldo May 18 '16 at 19:08

5 Answers5

3

In multiple inheritance, dynamic_cast will shift the pointer as necessary and is a must. Therefore, getting rid of it is dangerous. If your system extends to a point where it uses MI somewhere down, you might get all sorts of strange behaviour.

Cem Kalyoncu
  • 14,120
  • 4
  • 40
  • 62
  • `static_cast` and implicit conversions will also shift the pointer correctly, when moving up and down the class hierarchy, including with multiple inheritance. It's cross-casts that cannot work without `dynamic_cast`. – Ben Voigt May 18 '16 at 18:53
  • 1
    @CemKalyoncu it is not "safe" _in the presence of virtual inheritance_ in the sense that the compiler will not allow it. This quite often means it is perfectly safe to use in practice; you will get a compile-time error if it is not. – davmac May 19 '16 at 08:29
  • @CemKalyoncu: Please don't use ideone for snippets. They delete them, despite their terms of service saying that they will be hosted "forever". – Ben Voigt May 19 '16 at 15:13
  • That sample shows a case where no compiler error is given but the code works in correctly. That said it is not confined to MI only. It is for using static_cast for down casting. – Cem Kalyoncu May 19 '16 at 18:50
  • @BenVoigt Ok, which site you recommend? – Cem Kalyoncu May 19 '16 at 18:51
  • @CemKalyoncu if you mean that casting to the _wrong_ object type is possible, and breaks the type system, yes, `static_cast` is unsafe. But that is very different to what you are saying in your answer. `static_cast` will "shift the pointer as necessary" just as `dynamic_cast` does. The type-unsafe nature of `static_cast` doesn't cause a problem if you can be sure that the object you are casting is of the correct type. – davmac May 20 '16 at 09:33
  • I wasn't talking about dynamic_cast in that comment, if you read it all it ends with saying "It is for using static_cast for down casting.". I was trying to make a point that static_cast is not much safer than reinterpret_cast while performing down casting. – Cem Kalyoncu May 20 '16 at 10:24
2

You don't need dynamic_cast, dynamic_cast already use internal runtime type information to determine if it's able to cast the passed pointer/reference to the desired type but you are already checking it through the type() method. A static_cast is enough in your situation, as you are already providing RTTI by yourself.

Or alternatively you could just remove the type() method and use directly dynamic_cast, which yields nullptr if it's not able to cast the object to your desired type.

If you really want to avoid such things then you are forced to encapsulate the behavior desired into virtual methods such that wherever you need to use a Kid1 or Kid2, you will have different implementation without the need to really distinguishing between the two, just by letting polymorphism do its work.

Jack
  • 131,802
  • 30
  • 241
  • 343
  • Thanks for the answer. Your suggestion basically means that 50% of the casts will fail. Isnt that very undesirable? – Montaldo May 18 '16 at 19:01
  • You said that you need to distinguish them in your code, that's undesiderable too since it prevents polymorphism from doing its work. This is an XY problem. First explain why and how you need to distinguish them so that we can provide suitable solutions. – Jack May 18 '16 at 19:09
2

You could try the Visitor pattern.

When should I use the Visitor Design Pattern?

Community
  • 1
  • 1
Quokka
  • 174
  • 1
  • 3
  • 10
0

Maybe you could use typeid operator. Because:

N3337 5.2.8/2 says:

When typeid is applied to a glvalue expression whose type is a polymorphic class type (10.3), the result refers to a std::type_info object representing the type of the most derived object (1.8) (that is, the dynamic type) to which the glvalue refers ....

Example:

struct Base
{
    virtual void f(){} //to ensure that Base is polymorphic object
};

struct Derived: Base{};

struct AnotherDerived: Base{};



int main()
{
    Base *ptr = new AnotherDerived;

    if(typeid(*ptr) == typeid(Derived)) //Do not forget to dereference pointer, otherwise typeid() will return type_info for pointer itself
    {
         cout << "ptr is pointing to object of type Derived" << endl;
    }

    if (typeid(*ptr) == typeid(AnotherDerived))
    {
        cout << "ptr is pointing to object of type AnotherDerived" << endl;
    }

}

However if you have to distinguish between them in your code why are you using pointer to Base? What about separating code where you are using only Base properties and code where are you using specific properties of your child classes? That's the whole point of polymorphism.

EDIT: As davmac pointed out. It will work only if these subclasses will be most derived classes.

Guillaume Jacquenot
  • 11,217
  • 6
  • 43
  • 49
PcAF
  • 1,975
  • 12
  • 20
  • Thanks for your input. I am basically doing this with an extra piece of knowledge. I could do what @Jack suggest and simply "try". – Montaldo May 18 '16 at 19:33
  • @Montaldo When you need to distinguish between them why are you using pointer to Base? – PcAF May 18 '16 at 19:48
  • The self registering structure requires a common base type in order to be build from the factory. That is on one hand a blessing and beauty but the costs are this. – Montaldo May 18 '16 at 20:08
  • Carefully reading OP's question reveals why this answer is unfeasible. There is a base class `Base`, two subclasses `Kid1` and `Kid2`, which have further subclasses. OP only needs to differentiate between instances of `Kid1` and `Kid2` (_including_ instances of their respective subtypes); your answer instead shows how to check the _most derived_ type. (You could still use `typeid` in a suitable solution, but it would need to look different to your answer). – davmac May 20 '16 at 09:59
  • Thanks, I'll edit my answer. BTW: How would you do it (correctly) with `typeid`. – PcAF May 20 '16 at 10:38
0

I generally prefer to be able to get a handle to the desired type without using a cast. This means adding methods to the Base class:

virtual Kid1 *asKid1()
{
    return null;
}

virtual Kid2 *asKid2()
{
    return null;
}

The subclasses then override these as appropriate:

// (in Kid1)
virtual Kid1 *asKid1() override
{
    return this;
}

Although your Base class is still being "polluted" with extra methods, only these two methods are required, even if the subclasses are expanded in the future.

(You can have them return references rather than pointers, in which case they should throw an exception by default).

davmac
  • 20,150
  • 1
  • 40
  • 68
  • EDIT: I don't think this is good solution. `Base` shouldn't care about it's derived classes. What will you do when you want to create new class (deriving from `Base`)? You will have to create function for every derived class? – PcAF May 20 '16 at 08:38
  • @PcAF if you are using dynamic casts you are already in a situation where you care about derived classes. If the possibility exists that even _more_ derived classes that you will need to differentiate will exist in the future, you already have a design problem that extends far beyond adding a few methods to the base class. – davmac May 20 '16 at 09:03
  • Yes, you're right that when you have to use `dynamic_cast` you are in situation where you care about derived classes. However you care about it in specifcic context where you need to use properties of that derived class. I don't think that you will have to use derived classes everywhere where `Base` is used. – PcAF May 20 '16 at 09:17
  • @PcAF sure, and in those cases, you don't need to call the added methods. BTW one benefit of having "caster" methods is that they don't actually have to return the same object; this gives you some leeway for later design changes that a cast _doesn't_. Your free to have your own preferences but please accept that every decision comes with its own trade-offs. I personally prefer to avoid casts where possible. – davmac May 20 '16 at 09:24
  • I don't want to force you to use other solution. If you are comfortable with your solution use it. However I am commenting just because I want to find out benefits of other solutions and if they are comfortable to me I will use it. – PcAF May 20 '16 at 10:02