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;
};
First most of issues you're facing here are not about programming, are about design.
... 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). ...Well, don't do that, but then you have to realize that you're adding complexity to the code you need to implement such design desicion.
However, if every polygon can be "interpolated" then you should have a virtual function (or better yet a pure virtual function) in your
Polygon
class.Said that, with the code as it is, in order to add transparency to the API you declare you get_* functions as:
that way is clear for the user that he/she has to provide a reference to a vector of faces to get the result. Lets see how this change your code:
This way the down-cast is performed implicitly.
About your question: Is there a a better way? Yes, redesign your classes.
About your example:
I will ask you to think a moment about this:
At some point you will have to tell wich type you want call the function on.
Another approach you can try (I still recommend to redesign):
If you have the need of manipulating several distinct types in a uniform manner. Perhaps you want to take a look at Boot.Variant library.