Suppose I have this base class:
struct Vehicle {
//"op" stands for the operator of the vehicle
//examples: pilot, truck driver, etc.
virtual void insert_op(Op op) = 0;
//other members...
};
And these two subclasses
struct Truck : public Vehicle {
void insert_op(Op op) override {
//prepare Truck with truck driver
}
};
struct Airplaine : public Vehicle {
void insert_op(Op op) override {
//prepare Airplaine with pilot
}
};
As you guess, this is the other hierarchy:
struct Op {};
struct TruckDriver : public Op {};
struct Pilot : public Op {};
You already see the problem, don’t you? I want to FORCE Truck to accept only TruckDrivers and FORCE airplaines to accept only Pilots, but this is not possible in the current design. C++ does not allow diff parameters for overridden virtuals.
I guess I could do a run-time type check of the type of “Op” in each of the subclass implementations of insert_op, but that sounds like a really ugly solution, plus its not enforced at compile time.
Any ways out?
Your
Vehiclesaysvirtual void insert_op(Op op)which means “every vehicle can accept anyOp“.Therefore, according to your design, a
Truckisn’t a valid candidate for aVehiclesubclass because it can’t accept anyOp– it can accept onlyTruckDrivers.Related: Liskov substitution principle
The problem is in your design, not in implementation of it. I suggest to simplify your class hierarchy. Do you really need so many classes and inheritance? Can you simply go with
VehicleandOpthat have fields identifying their type?Let me further explain the design problem:
Assume some object
Awith a methodmanVehicle(Vehicle&).Truckis a subclass of Vehicle, so it’s possible to call this method with an object of typeTruck.However, the implementation of
Adoesn’t have a clue what concrete types ofVehicles. It only knows that all vehicles have a methodinsert_op(Op), so it’s valid for it to attempt a call likeinsert_op(Pilot())even if the vehicle is actually aTruck.Conclusions:
Compile-time check isn’t even possible
Runtime check could work…
Vehicles expects to be able to callinsert_op(Op)on anyVehicle.A solution would be to modify the
Vehicleinterface to look like:and document it so that the caller would know that
insert_opcan be only called withOps that satisfycan_insert_opon the givenVehicle. Or something analogous (like a documented exception frominsert_op“invalid op type for this vehicle”) – anything works as long as it’s a documented part of this interface.BTW technical remark: You’d probably want these methods to take the
Opby pointer or reference instead of copying it, to avoid an unnecessary copy as well as slicing.