在C++中存储多种结构的模式 std::<vector> 容器

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

本文关键字:lt vector 容器 gt std 模式 存储 C++ 结构      更新时间:2023-10-16

我有一个表示火车的数据结构,它可以由多种类型的汽车组成,例如火车引擎、粮食车、客车等:

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

算法将遍历列车上的车厢,并决定如何安排/分流每辆车厢(是否将其留在列车中,将其移动到列车的另一点,或将其从列车中移除)。这段代码看起来像:

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
    }
}

我不确定这种模式(带有dynamic_cast和if语句链)是否是处理不同类型的简单结构体列表的最佳方式。dynamic_cast的用法似乎不正确。

一种选择是将路由逻辑移动到结构中(例如(*it)->route(is_start_of_car, &some_other_data_structure…)),但是如果可能的话,我希望将路由逻辑保持在一起。

有没有更好的方法来迭代不同类型的简单结构体(没有方法)?还是保留dynamic_cast方法?

这个问题的标准解决方案称为双重分派。基本上,您首先将算法包装在单独的函数中,这些函数为每种类型的汽车重载:

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

然后,向car添加一个route方法,该方法在基类中是纯虚拟的,并在每个子类中实现:

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);
   }
}

你的循环看起来像这样:

std::vector<TrainCar*>::iterator it = cars.begin();
for (; it != cars.end(); ++it) {
    (*it)->route();
}

如果你想在运行时选择不同的路由算法,你可以将routeCar -函数包装在一个抽象基类中,并提供不同的实现。然后将该类的适当实例传递给TrainCar::route

如果类的数量是可管理的,您可以尝试boost::variant

在c++中使用"sum类型"有时会很混乱,所以它要么是这个,要么是双调度。

经典的OO解决方案是生成所有相关的函数在TrainCar的基类中创建virtual,并将具体逻辑放在每个基类中类。但是,您说希望保留路由逻辑如果可能的话一起。在某些情况下,这是合理的,而且这种情况下的经典解是一个变并(boost::variant,例如)。哪一种对你比较好,由你来决定。

妥协也是可能的。例如,人们可以很容易地想象一个路由逻辑在某种程度上独立于汽车类型的情况(并且您不希望在每种汽车类型中都复制它),但它确实如此取决于一定数量的汽车类型的特点。在这个在这种情况下,TrainCar中的虚函数可以简单地返回一个对象带有必要的依赖信息,供路由使用算法。这种解决方案的优点是减少了耦合在路由和TrainCar之间达到最小需要。

取决于这个信息的性质,以及它是怎样的使用时,返回的对象可以是多态的,具有继承性反映TrainCar的层次结构;在这种情况下,它必须是动态分配和管理:std::auto_ptr被设计为我就是这么想的