我正在用C ++编写一个3D网格模型,它具有不同的单元格类型,都存储在 Grid 类中的向量中。我已经定义了一个基本 GridCell 类,我还有两个派生类 GridCell1 和 GridCell2 。
现在在设置模型时,我读了一个文本文件,告诉我如何在 Grid 类中填充 gridCell 向量(std::vector<gridCell*> gridCellVector)
;意思是它告诉我push_back进入我的 gridCellVector 的派生单元的类型。
然后我读入另一个输入文件,其中包含我的 Grid 中每个 GridCell 的初始状态变量信息,按照第一个输入文件的顺序排列。
每个派生类( GridCell1 和 GridCell2 )都有一些状态变量(私有数据成员),而另一个则没有。当我在第二个输入文件中读取时,如何(或可以)访问和更新/初始化/设置派生类的数据成员?
我尝试过几个不同的东西,似乎只能返回 GridCell 基类中定义的get / set函数。当我逐步完成向量时,我无法弄清楚在使用每个派生的 GridCell 时如何访问派生类中的函数。
编辑:我很惊讶人们没有提到向下转发,除了说不使用dynamic_cast。我总是知道我正在更新 GridCell 的类型,因为我在第一个输入文件中读取时会跟踪加载到向量中的内容。既然我总是确定 GridCell 的类型,那么dynamic_cast不安全吗?
Double Edit:。因为我将 GridCell 对象传递给需要引用特定于传递对象的相应 GridCell 实例的数据成员和函数的其他函数,所以我实现了设计(我的模型的很多部分)目前没有通过集合。所以,就目前而言,我放弃了必须完全使用 GridCell 类型的想法,并且只会创建一个符合我所有需求的巨大的 GridCell 类。通过这种方式,我可以填写,然后访问我以后需要的任何数据成员和函数。
答案 0 :(得分:2)
如果您确定要使用两步流程,建议您使用GridCell
纯虚拟init
方法:
virtual void init(istream &) = 0;
然后在每个派生类中实现它。其目的是从文件中读取数据并初始化初始状态变量。
答案 1 :(得分:1)
正如其他人所说,最好一次读取这两个文件,并在创建派生类的同时进行派生类特定的初始化:
std::unique_ptr<GridCell> createGridCell1(std::istream& init) {
auto cell = std::make_unique<GridCell1>();
int value;
init >> value;
cell->setGridCell1State(value);
return cell;
}
std::unique_ptr<GridCell> createGridCell2(std::istream& init) {
// similarly to CreateGridCell1()...
}
std::vector<GridCell::Ptr> createCells(std::istream& types, std::istream& init) {
std::vector<GridCell::Ptr> cells;
std::string type;
while (types >> type) {
if (type == "GridCell1")
cells.push_back(createGridCell1(init));
else
cells.push_back(createGridCell2(init));
}
return cells;
}
int main() {
auto types = std::istringstream("GridCell1 GridCell2 GridCell1 GridCell1");
auto init = std::istringstream("1 2.4 2 3");
auto cells = createCells(types, init);
for (auto& cell : cells)
cell->put();
}
如果你必须在第二次传递中进行初始化,你可以使用Visitor pattern。你有某种GridCellVisitor
知道如何访问所有不同类型的网格单元:
class GridCellVisitor {
protected:
~GridCellVisitor() = default;
public:
virtual void visit(GridCell1& cell) = 0;
virtual void visit(GridCell2& cell) = 0;
};
您的网格单元格知道如何接受GridCellVisitor
:
class GridCell1 : public GridCell {
int state = 0;
public:
void setGridCell1State(int value) { state = value; }
void accept(GridCellVisitor& visitor) override { visitor.visit(*this); }
};
class GridCell2 : public GridCell {
double state = 0.0;
public:
void setGridCell2State(double value) { state = value; }
void accept(GridCellVisitor& visitor) override { visitor.visit(*this); }
};
通过这种方式,您可以将网格单元初始化的责任与来自网格单元本身的输入流分开,并避免在网格单元上进行脆弱的向下转换:
class GridCellStreamInitializer : public GridCellVisitor {
std::istream* in;
public:
GridCellStreamInitializer(std::istream& in) : in(&in){}
void visit(GridCell1& cell) override {
int value;
*in >> value;
cell.setGridCell1State(value);
}
void visit(GridCell2& cell) override {
double value;
*in >> value;
cell.setGridCell2State(value);
}
};
int main() {
auto in = std::istringstream("GridCell1 GridCell2 GridCell1 GridCell1");
auto cells = createCells(in);
auto init = std::istringstream("1 2.4 2 3");
auto streamInitializer = GridCellStreamInitializer(init);
for (auto& cell : cells)
cell->accept(streamInitializer);
}
缺点是GridCellVisitor
必须注意所有不同类型的网格单元格,因此如果添加新类型的网格单元格,则必须更新访问者。但据我所知,您读取初始化文件的代码必须知道所有不同类型的网格单元。
答案 2 :(得分:0)
您的vector<gridCell*>
只知道其元素的基类,因此只能调用gridCell
个函数。
我理解你的方法是首先使用指向正确派生类型的单元格的指针填充向量,而不是基类型。然后,对于每个单元格,您将读取类依赖数据。
最干净的方法是在基本单元中定义虚拟加载函数:
class gridCell {
...
virtual bool load (ifstream &ifs) {
// load the common data of all gridCells and derivates
return ifs.good();
}
};
虚拟函数将被派生的单元格覆盖:
class gridCell1 : public gridCell {
...
bool load (ifstream &ifs) override {
if (gridCell::load(ifs)) { // first load the common part
// load the derivate specific data
}
return ifs.good();
}
};
最后,您可以编写容器加载函数:
class Grid {
...
bool load (ifstream &ifs) {
for (auto x:gridCellVector)
if (!x->load(ifs))
break; // error ? premature end of file ? ...
}
};
您的问题看起来非常类似于序列化问题。你加载网格,也可能你写网格?如果你控制文件格式,并在一次通过中执行单元格的创建和加载,那么你不需要重新发明轮子并可以选择序列化库,如 boost::serialization
。