I have a data structure which represents a train, which can be made up of many types of car, for example the train engines, a grain car, a passenger car, and so on:
struct TrainCar {
// ...
Color color;
std::string registration_number;
unsigned long destination_id;
}
struct PowerCar : TrainCar {
// ...
const RealPowerCar &engine;
}
struct CargoCar : TrainCar {
// ...
const RealCargoCar &cargo;
bool full;
}
std::vector<TrainCar*> cars;
cars.push_back(new TrainCar(...));
cars.push_back(new TrainCar(...));
cars.push_back(new CargoCar(...));
cars.push_back(new CargoCar(...));
cars.push_back(new CargoCar(...));
An algorithm will iterate through the cars in the train, and decide how to route/shunt each car (whether to keep it in the train, move it to another point in the train, remove it from the train). This code looks like:
std::vector<TrainCar*>::iterator it = cars.begin();
for (; it != cars.end(); ++it) {
PowerCar *pc = dynamic_cast<PowerCar*>(*it);
CargoCar *cc = dynamic_cast<CargoCar*>(*it);
if (pc) {
// Apply some PowerCar routing specific logic here
if (start_of_train) {
// Add to some other data structure
}
else if (end_of_train && previous_car_is_also_a_powercar) {
// Add to some other data structure, remove from another one, check if something else...
}
else {
// ...
}
}
else if (cc) {
// Apply some CargoCar routing specific logic here
// Many business logic cases here
}
}
I am unsure whether this pattern (with the dynamic_casts, and chain of if statements) is the best way to process the list of simple structs of varying types. The use of dynamic_cast seems incorrect.
One option would be to move the routing logic to the structs (so like (*it)->route(is_start_of_car, &some_other_data_structure...)), however I'd like to keep the routing logic together if possible.
Is there a better way of iterating through different types of simple struct (with no methods)?, or do I keep the dynamic_cast approach?
The standard solution to this is called double-dispatch. Basically, you first wrap your algorithms in separate functions that are overloaded for each type of car:
void routeCar(PowerCar *);
void routeCar(CargoCar *);
Then, you add a route
method to car that is pure virtual in the base-class, and implemented in each of the subclasses:
struct TrainCar {
// ...
Color color;
std::string registration_number;
unsigned long destination_id;
virtual void route() = 0;
}
struct PowerCar : TrainCar {
// ...
const RealPowerCar &engine;
virtual void route() {
routeCar(this);
}
}
struct CargoCar : TrainCar {
// ...
const RealCargoCar &cargo;
bool full;
virtual void route() {
routeCar(this);
}
}
Your loop then looks like this:
std::vector<TrainCar*>::iterator it = cars.begin();
for (; it != cars.end(); ++it) {
(*it)->route();
}
If you want to choose between different routing-algorithms at run-time, you can wrap the routeCar
-functions in an abstract base class and provide different implementations for that. You would then pass the appropriate instance of that class to TrainCar::route
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With