Template for storing several types of structures in the std :: <vector> C ++ container

I have a data structure, which is a train, which can consist of many types of cars, for example, train engines, grain car, passenger car, etc.:

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(...)); 

The algorithm will go through the cars in the train and decide how to route / shunt each car (keep it in the train, move it to another point in the train, remove it from the train). This code is as follows:

 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'm not sure if this template (with dynamic_casts operators and if chain) is the best way to handle a list of simple structures of different types. Using dynamic_cast seems to be wrong.

One option would be to move the routing logic into structures (so (* it) -> route (is_start_of_car, and some_other_data_structure ...)), however I would like to combine the routing logic if possible.

Is there a better way to iterate using different types of simple structure (no methods) ?, or can I keep the dynamic_cast approach?

+4
source share
3 answers

The standard solution for this is called dual dispatch . Basically, you first wrap your algorithms with separate functions that are overloaded for each type of car:

 void routeCar(PowerCar *); void routeCar(CargoCar *); 

Then you add the route method to the car, which is pure virtual in the base class and is 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); } } 

Then your loop 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 runtime, you can wrap the routeCar functions in an abstract base class and implement different implementations for it. You will then pass the appropriate instance of this class to TrainCar::route .

+8
source

If the number of classes is controllable, you can try boost::variant .

Using "sum types" in C ++ is sometimes a mess, so this is either this or double dispatch.

0
source

A classic OO solution would be to make all the relevant functions virtual in the TrainCar base class, and put the specific logic in each class. However, you say you want to keep the routing logic together, if possible. There are cases where this is justified, and the classic solution in such cases is a variant union ( boost::variant , for example). You decide which is best in your case.

Compromises are possible. For example, you can easily imagine a case where the routing logic is somewhat independent of the type of vehicle (and you do not want to duplicate it in each type of vehicle), but it depends on a certain number of vehicle characteristics. In this case, a virtual function in TrainCar could simply return an object with the necessary dependent information that the routing algorithm will use. The advantage of this solution is to reduce traction between the route and TrainCar to the minimum necessary.

Depending on the nature of this information and how it is used, the returned object may be polymorphic, with its inheritance a hierarchy reflecting the value of TrainCar ; in this case, it should be dynamically allocated and managed: std::auto_ptr was designed with exactly this idiom in mind.

0
source

All Articles