Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Storing multiple-inheritance objects in container

Tags:

c++

oop

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!

like image 315
Ambarish Sridharanarayanan Avatar asked Dec 25 '22 16:12

Ambarish Sridharanarayanan


2 Answers

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.

like image 154
Mike Seymour Avatar answered Jan 08 '23 13:01

Mike Seymour


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();
}
like image 44
Matthieu M. Avatar answered Jan 08 '23 15:01

Matthieu M.