views:

1403

answers:

10

Let's say I have the following class structure:

class Car;
class FooCar : public Car;
class BarCar : public Car;

class Engine;
class FooEngine : public Engine;
class BarEngine : public Engine;

Let's also give a Car a handle to its Engine. A FooCar will be created with a FooEngine* and a BarCar will be created with a BarEngine*. Is there a way to arrange things so a FooCar object can call member functions of FooEngine without downcasting?

Here's why the class structure is laid out the way it is right now:

  1. All Cars have an Engine. Further, a FooCar will only ever use a FooEngine.
  2. There are data and algorithms shared by all Engines that I'd rather not copy and paste.
  3. I might want to write a function that requires an Engine to know about its Car.

As soon as I typed dynamic_cast when writing this code, I knew I was probably doing something wrong. Is there a better way to do this?

UPDATE:

Based on the answers given so far, I'm leaning towards two possibilities:

  1. Have Car provide a pure virtual getEngine() function. That would allow FooCar and BarCar to have implementations that return the correct kind of Engine.
  2. Absorb all of the Engine functionality into the Car inheritance tree. Engine was broken out for maintenance reasons (to keep the Engine stuff in a separate place). It's a trade-off between having more small classes (small in lines of code) versus having fewer large classes.

Is there a strong community preference for one of these solutions? Is there a third option I haven't considered?

+2  A: 

Would it be possible for the FooCar to use the BarEngine?

If not, you might want to use an AbstractFactory to create the right car object, with the right engine.

Calyth
If I understand the OP's question, that won't help him because it's the inherited "engine" member variable of type Engine that's giving him headaches... he wants it to be covariant, which I don't think is allowed in C++, so I'm refraining from posting a non-answer...
rmeador
A: 

Allowing I haven't missed something, this should be rather trivial.

The best way to do this is to create pure virtual functions in Engine, that are then required in derived classes that are to be instantiated.

An extra credit solution would probably be to have an interface called IEngine, that you instead passs to your Car, and every function in IEngine is pure virtual. You could have a 'BaseEngine' that implements some of the functionality you want (i.e., 'shared') and then have leaf ones off of that.

The interface is nice should you ever have something you want to 'look' like an engine, but probably isn't (i.e., mock test classes and such).

Eddie Parker
A: 

Is there a way to arrange things so a FooCar object can call member functions of FooEngine without downcasting?

Like this:

class Car
{
  Engine* m_engine;
protected:
  Car(Engine* engine)
  : m_engine(engine)
  {}
};

class FooCar : public Car
{
  FooEngine* m_fooEngine;
public:
  FooCar(FooEngine* fooEngine)
  : base(fooEngine)
  , m_fooEngine(fooEngine)
  {}
};
ChrisW
I can see this design causing all sorts of problems. What if Car defines a void setEngine(Engine* engine) method? An external client could call it on FooCar and now the integrity of your object has gone.
Andy
If Car defines a "void setEngine(Engine* engine)" method, then this method would need to be, either protected, or abstract. Or, an alternative to having "Engine* m_engine" in Car would be to have a private abstract "Engine* get_engine()" method in Car.
ChrisW
There won't be a setEngine function in my case, but I agree this might cause maintenance problems. It's confusing for subclasses of Car to have two handles (with different types!) to the same Engine.
Kristo
+8  A: 

Just one thing I wanted to add: this design already smells bad to me because of what I call parallel trees.

Basically if you end up with parallel class hierarchies (as you have with Car and Engine) then you're just asking for trouble.

I would rethink if Engine (and even Car) needs to have subclasses or those are all just different instances of the same respective base classes.

cletus
Can you provide a concrete example (given my class structure above) of how one might solve the "parallel trees" problem?
Kristo
+15  A: 

I'm assuming that Car holds an Engine pointer, and that's why you find yourself downcasting.

Take the pointer out of your base class and replace it with a pure virtual get_engine() function. Then your FooCar and BarCar can hold pointers to the correct engine type.

Shmoopty
and since virtual functions allow covariant return types, you can make the returned value be of the appropriate FooEngine or BarEngine type! +1 for this simple solution I overlooked.
rmeador
This is The Way To Go. @Kristo, try it.
Daniel Daranas
IMHO that just kicks the downcasting problem can down the street.
cletus
How does this postpone the downcasting problem?
Kristo
Kristo: look up covariant return types and you shall understand. The key is to not specify the member variable that actually stores the engine in the superclass -- only ever use this abstract accessor, which in the child classes is overridden to use a member variable of the appropriate type.
rmeador
@rmeador: I understand your answer. I should have tagged my comment with "@cletus".
Kristo
+2  A: 

You can store the FooEngine in FooCar, BarEngine in BarCar

class Car {
public:
  ...
  virtual Engine* getEngine() = 0;
  // maybe add const-variant
};

class FooCar : public Car
{
  FooEngine* engine;
public:
  FooCar(FooEngine* e) : engine(e) {}
  FooEngine* getEngine() { return engine; }
};

// BarCar similarly

The problem with this approach is that getting an engine is a virtual call (if you're concerned about that), and a method for setting an engine in Car would require downcasting.

jpalecek
I second your answer. But I ask: Why would one "set" the FooEngine of a FooCar? A car constructor should build also the car's own engine: FooCar() {engine = new FooEngine(); } Engine* getEngine() { return engine; }
Federico Ramponi
It depends entirely on what the OP wants to do. Real cars' engine can be changed, there is even a legal procedure for that. So if he wants to model that, he'll have to do some sort of setEngine() method.
jpalecek
The Car's Engine will never be changed in this case.
Kristo
+4  A: 

I don't see why a car can't be composed of an engine (if BarCar will always contain BarEngine). Engine has a pretty strong relationship with the car. I would prefer:

class BarCar:public Car
{
   //.....
   private:
     BarEngine engine;
}
Sridhar Iyer
I don't think this will work for me because the Car base class needs to do things with its Engine.
Kristo
Of course.. and what is stopping the private methods of Car to call the Engine's public methods? Unless you are trying to abstract it further.. i.e. Car class consists of Engine pointer to start with.
Sridhar Iyer
+4  A: 

You could also templatize the Engine type as follows

template<class EngineType>
class Car
{
    protected:
        EngineType* getEngine() {return pEngine;}
    private:
        EngineType* pEngine;
};

class FooCar : public Car<FooEngine>

class BarCar : public Car<BarEngine>
JohnMcG
If I understand this right, this is basically equivalent to the answers that suggest a pure virtual getEngine function. It's compile-time vs. run-time polymorphism, no?
Kristo
Yeah, except this is a bit more restrictive, in that it mandates that you actually hold a pointer, whereas with the pure virtual functions, subclasses are free to implement it as they see fit.
JohnMcG
+1  A: 

I think that it depends if Engine is only privately used by Car and its children or if you also want to use it in other objects.

If Engine functionalities are not specific to Cars, I would use a virtual Engine* getEngine() method instead of keeping a pointer in the base class.

If its logic is specific to Cars, I would prefer putting the common Engine data/logic in a separate object (not necessarily polymorphic) and keep FooEngine and BarEngine implementation in their respective Car child class.

When implementation recycling is more needed than interface inheritance, object composition often offer greater flexibility.

total
A: 

Microsoft's COM is kind of clunky, but it does have a novel concept - if you have a pointer to an interface of an object, you can query it to see if it supports any other interfaces using the QueryInterface function. The idea is to break your Engine class into multiple interfaces so that each can be used independently.

Mark Ransom