我正在编写一个科学代码,需要创建由一组面定义的三维单元格,这些面由一组顶点定义。
这三个类(Cell
,Face
,Vertex
)分别来自一些通用几何类(Polyhedron
,Polygon
,{{1}实现一些几何例程,如Point
。
Polygon::CalculateArea()
课程为Face
课程添加了科学所需的其他数据和功能,例如Polygon
。我不想让这些成员函数在基类(Face::Interpolate()
)中虚拟化。
现在,问题。我使用指向Polygon
的指针向量初始化Cell
,该指针由基类Face
构造函数处理,该构造函数将Polyhedron
向上转换为Face*
:< / p>
Polygon*
稍后,我想访问存储在Polyhedron::Polyhedron( std::initializer_list<Polygon*> polygons );
中的Face*
,以便我可以调用Cell
,但它已存储为Face::Interpolate()
,因此没有成员函数Polygon*
。我可以将它手动转发回Polygon::Interpolate()
,但它不是很干净。代码的用户必须执行以下操作:
Face*
这不明显。
我希望界面透明,这样才能正常工作:
Face * temp_face = (Face*)cell->GetFaces()[0]; // Could use static_cast
temp_face->Interpolate();
我可以想到两到三种方法来实现这一目标。我正在寻找更好的解决方案或建议反馈哪些:
在目前刚刚从cell->GetFaces()[0]->Interpolate();
继承的Cell::GetFaces()
中,我可以创建一个将Polyhedron::GetPolygons()
复制到新向量std::vector<Polygon*>
的包装器。这对我来说似乎很草率,不易维护,效率低下且容易出错。
我可以存储std::vector<Face*>
,而不是存储std::vector<Polygon*>
。根据我的理解,这些智能指针保留了类型感知,以便它们可以调用正确的析构函数,但是它们可能只是根据实现存储对析构函数的引用。我不想将shared_ptr用于性能目的 - 我知道他们很友善,但我创造了数以百万计的这些多边形并且很容易在正确的地方销毁它们。由于std::vector<std::shared_ptr<Polygon>>
构造函数中使用的复制构造函数,我无法轻松使用unique_ptr
。
模板整个Polyhedron类,用std::initializer_list
替换Polygon*
的每个实例,并检查F*
是F
的基础:
Polygon
然后从具有给定typename的父级继承:
template<typename F = Polygon>
typename std::enable_if<std::is_base_of<Polygon, F>::value, void>::type
class Polyhedron
这对我来说似乎是最好的方法,因为它具有最少的样板,并且没有任何暴露给用户;但它仍然感觉很乱,特别是在&#34;真实&#34;可能有多种类型必须指定的情况:
class Cell : public Polyhedron<Face>
有更好的方法吗?也许是在原始载体(或其他容器)中保留类型的方法?
如果没有,上述哪种方法是最佳做法,为什么?
修改 这是一个抽象的问题视图。尝试运行sumOfSomethingSpecific()时会发生此问题。在我的实际问题中,该函数位于派生类Derived_B内,该类旨在与Derived_A一起使用,但为了解决问题,它没有任何区别。
class Cell: public Polyhedron<Face,Vertex,type3,type4,type5,...>
答案 0 :(得分:2)
您在这里遇到的第一个问题不是关于编程,而是关于设计。
...具有科学所需的其他数据和功能的课程,例如Face::Interpolate()
。我不想在基类(Polygon)中使这些成员函数虚拟化。 ... 的
好吧,不要这样做,但是你必须意识到你增加了实现这种设计需求所需的代码的复杂性。
但是,如果每个多边形都可以“插值”,那么你应该在Polygon
类中拥有一个虚函数(或者更好的纯虚函数)。
说,使用代码,为了增加API的透明度,您声明get_ *函数为:
void GetFaces(std::vector<Face *> &faces);
这种方式对于用户来说是清楚的,他/她必须提供对面向量的引用以获得结果。让我们看看这是如何改变您的代码的:
// Face * temp_face = (Face*)cell->GetFaces()[0]; // Could use static_cast
std::vector<Face *> temp_faces;
cell->GetFaces(temp_faces);
//temp_face->Interpolate();
temp_faces[0]->Interpolate();
这样可以隐式执行向下转换。
关于您的问题:有更好的方法吗?是的,重新设计课程。
我会请你思考一下:
struct Base {};
struct Derived_A: Base { double doSomethingSpecific(); };
struct Derived_B: Base { double doSomethingSpecific(); };
int main()
{
std::vector<Base*> base_v = {/*suppose initialization here*/};
base_v[0]->doSomethingSpecific(); // Which function must be called here?
// Derived_A::doSomethingSpecific or
// Derived_B::doSomethingSpecific.
}
在某些时候,您必须告诉您想要调用该函数的类型。
您想要的抽象级别在C ++中不存在。编译器需要知道对象的类型才能执行(编译)对其成员函数之一的调用。
如果你有需要以统一的方式操纵几种不同的类型。也许您想看一下Boot.Variant库。
答案 1 :(得分:-1)
我在一个项目中遇到了类似的问题。我使用的解决方案是将实际对象的所有权赋予最派生类,为基类提供对象的副本,并使用虚函数在添加/删除对象时使副本保持最新:
class Polyhedron {
protected:
bool _polygons_valid = false;
std::vector<Polygon*> _polygons;
virtual void RebuildPolygons() = 0;
public:
std::vector<Polygon*>& GetPolygons()
{
if (!_polygons_valid) {
RebuildPolygons();
_polygons_valid = true;
}
return _polygons;
}
/*Call 'GetPolygons()' whenever you need access to the list of polygons in base class*/
};
class Cell: public Polyhedron {
private:
std::vector<Face*> _faces; //Remember to set _polygons_valid = false when modifying the _faces vector.
public:
Cell(std::initializer_list<Face*> faces):
_faces(faces) {}
//Reimplement RebuildPolygons()
void RebuildPolygons() override
{
_polygons.clear();
for (Face* face : _faces)
_polygons.push_back(face);
}
};
这种设计具有明确所有权的好处(大多数派生类是所有者),并且只在需要时才复制和向上转换对象指针的向量。缺点是你有两份基本相同的东西;指向对象的向量。设计也非常灵活,因为从Polyhedron
派生的任何类只需使用从RebuildPolygons()
派生的任何类型的向量来实现Polygon
函数。