从Base*容器向下强制转换为Derived*容器,无需显式转换
Downcast from a container of Base* to Derived* without explicit conversion
我正在编写一个科学代码,需要创建三维细胞,由一组面定义,由一组顶点定义。
这3个类(Cell
, Face
, Vertex
)分别是由一些一般的几何类(Polyhedron
, Polygon
, Point
)推导而来,它们实现了一些类似Polygon::CalculateArea()
的几何例程。
Face
类在Polygon
类的基础上增加了科学所需的额外数据和功能,如Face::Interpolate()
。我不想让这些成员函数在基类(Polygon
)中成为虚函数。
现在,问题是。我用指向Face
的指针向量初始化Cell
,这是由基类Polyhedron
构造函数处理的,它将Face*
向上转换为Polygon*
:
Polyhedron::Polyhedron( std::initializer_list<Polygon*> polygons );
稍后,我想访问存储在Cell
中的Face*
,以便我可以调用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<std::shared_ptr<Polygon>>
而不是存储std::vector<Polygon*>
。据我所知,这些智能指针保留了类型意识,因此它们可以调用正确的析构函数,但它们可能只是存储对析构函数的引用,具体取决于实现。我不想为了性能目的而使用shared_ptr——我知道它们很好也很友好,但我创建了数百万个这样的多边形,很容易在正确的地方破坏它们。我不能轻易地使用unique_ptr
,因为std::initializer_list
构造函数中使用了复制构造函数。模板整个多面体类,用
F*
替换Polygon*
的每个实例,并检查F
是Polygon
的基础:template<typename F = Polygon> typename std::enable_if<std::is_base_of<Polygon, F>::value, void>::type class Polyhedron
,然后从具有给定typename的父类继承:
class Cell : public Polyhedron<Face>
对我来说,这似乎是最好的方法,因为它具有最少的样板文件,并且没有向用户暴露任何内容;但是它仍然感觉很乱,特别是在可能有多个类型都必须指定的"实际"情况下:
class Cell: public Polyhedron<Face,Vertex,type3,type4,type5,...>
有更好的方法吗?可能是在原始向量(或其他容器)中保留类型的一种方法?
如果不是,上面哪个方法是最佳实践,为什么?
编辑:这是对这个问题的抽象看法。当尝试运行sumOfSomethingSpecific()时,会出现这个问题。在我的实际问题中,该函数位于派生类Derived_B中,该类设计用于与Derived_A一起工作,但为了解决问题,它没有区别。
class Base_A
{
public:
Base_A();
~Base_A();
// I don't want virtual doSomethingSpecific() here.
};
class Derived_A
{
public:
using Base_A::Base_A;
double doSomethingSpecific();
};
// I could template this whole class
// template <typename T>
// where T replaces Base_A
class B
{
public:
// This can be initialized with:
// std::vector<Derived_A*>
// which is what I want to do, but we lose info about doSomethingSpecific()
// even if I write a separate constructor its still stored as
// std::vector<Base_A*>
B(std::vector<Base_A*> v) : v(v) {};
~B();
double sumOfSomethingSpecific()
{
double sum = 0;
for(auto&& A : v) {
// Can't do this, A is a pointer of type Base_A*, but this is the abstraction that I want to achieve
sum += A->doSomethingSpecific();
// Could do this, but its ugly and error-prone
Derived_A* tempA = (Derived_A*)A;
sum += tempA->doSomethingSpecific();
}
return sum;
}
protected:
std::vector<Base_A*> v;
};
首先,你在这里面临的大多数问题都不是关于编程,而是关于设计。
…类,提供科学所需的附加数据和功能,如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。变种库。
我在我的一个项目中遇到了类似的问题。我使用的解决方案是将实际对象的所有权赋予最派生的类,为基类提供对象的副本,并使用虚函数在添加/删除对象时保持副本的最新状态:
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
派生的任何类型的向量。
- 在提升multi_index容器中,是否定义了"default index"?
- 用于访问容器<T>数据成员的正确 API
- Eigen如何在容器循环中干净地附加矩阵
- 模板专用化(按容器):value_type
- 关联容器的下界复杂性:成员函数与非成员函数
- 更多constexpr容器是否需要mark_immutable_if_consexpr
- 在STL容器中使用模板类
- 当有分配器意识的容器被复制/移动时,反弹分配器是否被复制/移走
- 检查函数返回类型是否与STL容器类型值相同
- STL算法函数在多个一维容器上的使用
- 如何将带有自定义对象的容器从C++传递到QML
- 在没有未定义行为的情况下实现类似std::vector的容器
- 将sharet_ptr<Derived>转换为shared_ptr<Base>
- 将 BASE 派生类存储在同一容器中
- 为什么 STL 容器适配器堆栈中的 top 返回常量引用?
- 在C++中迭代 2D 容器的最干净方法
- 将线程中的数据存储到全局容器的最佳方法?
- 如果我真的真的想从 STL 容器继承,并且我继承构造函数并删除新运算符,会发生什么?
- 使用带有C++对象和标准库容器的插件系统
- 从Base*容器向下强制转换为Derived*容器,无需显式转换