I am writing a scientific code which needs to create 3-dimensional cells, defined by a set of faces, which are defined by a set of vertices.
These 3 classes (Cell
, Face
, Vertex
) are derived respectively from some generic geometry classes (Polyhedron
, Polygon
, Point
) which implement some geometric routines like Polygon::CalculateArea()
.
The Face
class adds to the Polygon
class with additional data and functions required for the science, like Face::Interpolate()
. I don't want to make these member functions virtual in the base class (Polygon
).
Now, the problem. I initialize a Cell
with a vector of pointers to Face
, which is handled by the base class Polyhedron
constructor, which upcasts the Face*
to Polygon*
:
Polyhedron::Polyhedron( std::initializer_list<Polygon*> polygons );
Later, I want to access the Face*
stored in a Cell
so that I can call Face::Interpolate()
, but it has been stored as a Polygon*
and thus has no member function Polygon::Interpolate()
. I can downcast it manually back to a Face*
which works, but is not very clean. The user of the code has to do something like:
Face * temp_face = (Face*)cell->GetFaces()[0]; // Could use static_cast
temp_face->Interpolate();
which is not obvious.
I want the interface to be transparent, so that this just works:
cell->GetFaces()[0]->Interpolate();
I can think of two or three ways to achieve this. I'm looking for a better solution or feedback of which of these is recommended:
In
Cell::GetFaces()
which currently just inherits fromPolyhedron::GetPolygons()
I could create a wrapper that copies thestd::vector<Polygon*>
to a new vectorstd::vector<Face*>
. This seems sloppy to me, not easy to maintain, inefficient and prone to errors.Instead of storing
std::vector<Polygon*>
I could storestd::vector<std::shared_ptr<Polygon>>
. From what I understand, these smart pointers retain type-awareness so that they can call the right destructor, but they might just store a reference to the destructor depending on implementation. I don't want to use shared_ptr for performance purposes -- I know they're good and friendly, but I'm creating millions of these Polygons and its easy to destroy them in the right place. I can't useunique_ptr
easily because of the copy-constructor used instd::initializer_list
constructors.Template the whole Polyhedron class, replacing every instance of
Polygon*
withF*
and checking thatF
is a base ofPolygon
:template<typename F = Polygon> typename std::enable_if<std::is_base_of<Polygon, F>::value, void>::type class Polyhedron
and then inheriting from a parent with a given typename:
class Cell : public Polyhedron<Face>
This seems like the best method to me, since it has the least boilerplate and nothing exposed to the user; but it still feels messy, especially in the "real" case where there might be multiple types that would all have to be specified:
class Cell: public Polyhedron<Face,Vertex,type3,type4,type5,...>
Is there a a better way? Perhaps a means of retaining type in the original vector (or some other container)?
If not, which of the above methods is the best practice and why?
Edit: Here's an abstracted view of the problem. The problem occurs when trying to run sumOfSomethingSpecific(). In my actual problem, that function is inside a derived class Derived_B, which is designed to work with Derived_A, but for the sake of the problem, it makes no difference.
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;
};