C++ inheritance, interfaces

175 views Asked by At

Let's say I do have an interface IControllable, 3 classes inherits that interface: MachineControllable, LightControllable, OtherControllable which has some specific data and methods to them.

Now I do want to have only one container of all IControllable, so I do create a vector container.

vector<IControllable> allControllables; // and put all the MachineControllable,
//LightControllable, OtherControllable here by the IControllable interface class.

But the problem now, that I can only use what the IControllable defines, not the specific data and methods of specific Controllable.

Should I have seperate containers for each Controllable, or how my logic is wrong in terms of OOP ?

4

There are 4 answers

10
πάντα ῥεῖ On

"Should I have seperate containers for each Controllable, or how my logic is wrong in terms of OOP ?"

No, your logic is OK. The problem is, you can't instatiate an abstract class.

You should have a container keeping pointers to the IControllable interface, like:

 vector<IControllable*> allControllables;

or

 vector<std::unique_ptr<IControllable>> allControllables;

or

 vector<std::shared_ptr<IControllable>> allControllables;
2
James Kanze On

That's probably not the only problem. Containers require value semantics, and contain copies of the object you put into them. Inheritance doesn't generally work well with copy and assignment: think of what it might mean if you have:

*p = *q;

where p and q are IControllable*, but p points to a MachineControllabe, but q to a LightControllable. As a general rules (and there are exceptions), you should make interfaces uncopiable and unassignable. Also, typically, they will also contain pure virtual functions, which means that you can't instantiate them. In either case, std::vector<IControllable> wouldn't even compile.

What you probably want is std::vector<IControllable*>. Polymorphism only works through pointers or references anyway (and you cannot have a vector of references).

3
Mark Ransom On

As mentioned, your vector should contain pointers of some kind, not the base object itself.

Once you have that, you need a way to get the specific pointer to the actual object type in the container. The standard way is with dynamic_cast:

IMachineControllable * p = dynamic_cast<IMachineControllable*>(allControllables[i]);

If you choose the wrong type, you'll get a NULL pointer.

If these are Microsoft COM interfaces there's an alternate method you need to use instead, but I won't go into that unless you state that's what you have.

0
Galik On

But the problem now, that I can only use what the IControllable defines, not the specific data and methods of specific Controllable.

Should I have seperate containers for each Controllable, or how my logic is wrong in terms of OOP ?

This depends on what you want to do with your objects.

If you need to make heavy use of all the IControllable objects through the IControllable interface then putting them all in one container makes sense.

If, on the other hand, you want to make heavy use of their specific interfaces then having separate containers also makes sense.

If you need to do both then there is nothing wrong with doing both.

Now if you choose to put everything in one container then you must use some kind of pointer/smart pointer because storing different types by value would cause slicing and would not allow polymorphic execution.

However It is preferable to store objects in containers by value if possible. So if you go with multiple containers storing by value would be preferable.

If you want to do both then you could store the objects by value in separate containers and store their pointer in a catch-all container. In that case the stored-by-value containers will own the objects and so the catch-all container should not own them - use raw pointers for that:

struct IControllable { virtual ~IControllable() {} };
struct MachineControllable: IControllable {};
struct LightControllable: IControllable {};
struct OtherControllable: IControlable {};

// store by value if possible (not always possible)
std::vector<MachineControllable> machingControlables;
std::vector<LightControllable> lightControlables;
std::vector<OtherControlable> otherControlables;

std::vector<IControlable*> allControlables; // raw pointers (non owned)

If you don't want to store your objects separately then your catch-all container needs to own the objects:

// these objects die when this container dies.
std::vector<std::unique_ptr<IControlable>> allControlables;

So the real question is how are you going to spend most of your time processing these as specific types and/or general (base) types?

And also how complex do you want your data structures? If you use multiple containers then that increases the complexity of managing the data.

Bear in mind if you don't use separate containers for your specific types you will have to cast them to make specific calls:

for(auto& controlable: allControlables)
{
    MachineControllable* mc;
    LightControllable* lc;
    OtherControllable* oc;

    if((mc = dynamic_cast<MachineControllable*>(controlable.get())))
        mc->machine_specific();
    else if((lc = dynamic_cast<LightControllable*>(controlable.get())))
        lc->light_specific();
    else if((oc = dynamic_cast<OtherControllable*>(controlable.get())))
        oc->other_specific();
}

Not ideal.