4

A library I use has many types, all of which derive from the same 2 interfaces:

class Huey : public IDuck, public ICartoonCharacter
{
...
};

class Dewey : public IDuck, public ICartoonCharacter
{
...
};

class Louie : public IDuck, public ICartoonCharacter
{
...
};

I'd like to store objects of all the above types in a wrapper class and stick objects of that wrapper class in a container. Of course I should be able to call methods belonging to both interfaces from my wrapper class.

What are my options here? I could think of

  • storing IDuck *s in my wrapper and dynamic_cast-ing to ICartoonCharacter, or
  • using something like boost::any while making my wrapper a class-template, with a couple of static_asserts to ensure the template parameter inherits from IDuck and ICartoonCharacter.

but neither option particularly appeals. Any ideas?

two interfaces, multiple inheritance combine into one container? is a related question, but James Kanze's answer doesn't work for me, as I can't change the 3 classes.

EDIT: Don't use multiple inheritance often, had forgotten syntax. Now inheriting publicly from both interfaces.

EDIT: Now using dynamic_cast instead of static_cast (which won't work).

EDIT: I found both Mike Seymour's and Matthieu M's answers promising. I'll accept one of their answers once I've coded it all up. Thanks!

Community
  • 1
  • 1
  • 2
    You can't static_cast from `IDuck*` to the unrelated type `ICartoonCharacter*`. You could dynamic_cast provided `IDuck` has at least one virtual function. – Steve Jessop Dec 13 '13 at 09:15
  • I usually use the first option, if using `dynamic_cast` is possible (see @SteveJessop comment above). – Kiril Kirov Dec 13 '13 at 09:15
  • I'm not sure, but I think even if your children classes cannot be changed to inherit from a single parent (who in turn inherits from both current parents,) you can still benefit from introducing such a class (e.g. `ICartoonDuck`.) Your can store `ICartoonDuck`s in your container and worry less about memory layout problems. (You still need unsafe casts though; this is still a hack.) – yzt Dec 13 '13 at 09:16
  • @yzt - that's an idea, if all classes inherit `IDuck` and `ICartoonCharacter`. Otherwise, it would impossible (unless interfaces are terribly mixed) – Kiril Kirov Dec 13 '13 at 09:18
  • 1
    @Ambarish Sridharanarayanan - is the second inheritance still `public`? Or `public` keyword is really missing (meaning - private inheritance)? – Kiril Kirov Dec 13 '13 at 09:19
  • @KirilKirov: Yeah, if all the inheritance scopes and orders are the same, having the `ICartoonDuck` class would help the *client* code to be mostly free of unsafe casts and only the container class needs to have unsafe constructs (along with some compile-time and runtime checks to assure safety as much as possible.) – yzt Dec 13 '13 at 09:24
  • @KirilKirov: oops, yes, they're all public inheritance. – Ambarish Sridharanarayanan Dec 13 '13 at 20:58
  • @SteveJessop: doh, I'd have to use dynamic_cast, or in the absence of RTTI, reinterpret_cast, yes. – Ambarish Sridharanarayanan Dec 13 '13 at 20:59
  • 1
    @AmbarishSridharanarayanan: `reinterpret_cast` doesn't do it. It will compile but because of the multiple inheritance it can give the wrong answer. The only thing that potentially might make it work is if the empty base class optimization means the `IDuck` and `ICartoonCharacter` base class sub-objects have the same address, but this is not guaranteed. – Steve Jessop Dec 14 '13 at 00:07

3 Answers3

5

A simple option is to store two pointers in the wrapper:

struct CartoonDuckWrapper {
    IDuck * duck;
    ICartoonCharacter * toon;

    template <class CartoonDuck>
    CartoonDuckWrapper(CartoonDuck & cd) : duck(&cd), toon(&cd) {}
};

There's no particular need to use static_assert to check that CartoonDuck inherits from both base classes, although that might give slightly better diagnostics than simply letting the pointer conversions fail.

If the base classes are polymorphic (which, being interfaces, they probably are), you could save the space of one pointer, in exchange for a run-time cost, by using dynamic_cast to convert one to the other. static_cast can't be used for such a "cross-cast" between base classes.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
3

As all issues in programming, you can solve it by adding one more level of indirection.

class ICartoonDuck: public IDuck, public ICartoonCharacter {};

template <typename T>
class CartoonDuck: public ICartoonDuck {
public:
    explicit CartoonDuck(T t): _t(std::move(t)) {}

    // IDuck interface
    virtual void foo() override { t.foo(); }

    // ICartoonCharacter interface
    virtual void bar() override { t.bar(); }

private:
    T _t; // or any ownership scheme that makes sense
}; // class CartoonDuck

template <typename T>
CartoonDuck<T> makeCartoonDuck(T t) { return CartoonDuck(std::move(t)); }

template <typename T, typename... Args>
std::unique_ptr<CartoonDuck<T>> makeUniqueCartoonDuck(Args&&...) {
    return std::unique_ptr<CartoonDuck<T>>(new T(std::forward<Args>()...);
}

Now, you can happily store std::unique_ptr<ICartoonDuck> in your container.

This can be used as:

std::vector<std::unique_ptr<ICartoonDuck>> cartoonDucks;
cartoonDucks.push_back(makeUniqueCartoonDuck<Huey>());
cartoonDucks.push_back(makeUniqueCartoonDuck<Dewey>());
cartoonDucks.push_back(makeUniqueCartoonDuck<Louie>());

for (std::unique_ptr<ICartoonDuck> const& cd: cartoonDucks) {
    cd->foo();
    cd->bar();
}
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • if i understand correctly `Cartoon Duck` will hold `Huey` `louie` etc right? – Koushik Shetty Dec 13 '13 at 09:46
  • @Koushik: Yes, that is the idea, let me make an example as apparently it's not as obvious as I thought it would be. – Matthieu M. Dec 13 '13 at 09:51
  • i like this solution as it feels like the idimatic c++ ways.+1 – Koushik Shetty Dec 13 '13 at 10:03
  • @Koushik: it is more "object-oriented" maybe, but I feel obligated to mention that Mike's implementation does not requires another allocation (though it does not handle ownership, either); so really it's a matter of requirements/fine-tuning :) – Matthieu M. Dec 13 '13 at 10:10
2

Create an intermediate class:

class ILuckyDuck: public IDuck, ICartoonCharacter //...

with:

class Huey : public ILuckyDuck //...

etc, and store:

std::vector<std:shared_ptr<ILuckyDuck>> donald;
Paul Evans
  • 27,315
  • 3
  • 37
  • 54