Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pattern for storing multiple types of struct in a C++ std::<vector> container

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?

like image 367
checkers Avatar asked Feb 22 '23 17:02

checkers


1 Answers

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.

like image 158
Björn Pollex Avatar answered Feb 25 '23 07:02

Björn Pollex