I'm currently working in a first C project as an attempt to learn the language through experience. It has been quite challenging but up until now I've managed to tackle most problems by myself and the help of the internet.
However, as of recently I am stuck on something for which I simply cannot find a satisfying solution so I would like to hear the opinion of more experienced programmers.
The problem is the following:
- On one hand I have an abstract
BaseShape
class from which several concrete classes inherit such asTriangle
,Circle
,Rectangle
, etc. - On the other hand I have a template
RenderCommand<Shape>
class which has a specialized definition for each concrete shape. - Finally I have an array that contains multiple mixed shapes as
BaseShape
pointers.
My question now is what would be the best way to create specialized RenderCommand
instances from these BaseShape
pointers? I am currently thinking of either dynamic casting or trying some polymorphism with virtual methods but neither feel right.
EDIT
For now I've settled with a solution that does the trick but I am aware that it is likely not following several best practices so I am still open to other suggestions.
To clarify things a bit I've also added some pseudocode explaining a bit the layout of the class definitions.
RenderCommands.h
struct BaseRenderCommand
{
virtual execute() = 0;
... // contains stuff on cameras and other render options
}
template<class Shape>
struct RenderCommand : public BaseRenderCommand
{
RenderCommand(Shape *shape) {...};
virtual execute();
...
}
Shapes.h
struct BaseShape
{
... // Typical shape info like center position
}
struct Triangle : public BaseShape
{
...
}
struct Circle : public BaseShape
{
...
}
struct Rectangle : public BaseShape
{
...
}
CodePudding user response:
Here is a way you could potentially tackle this. I'm moving you away from a runtime construct (polymorphism
) to a compile time construct std::variant
.
Without any code I have to make a ton of assumptions here. Also note that I included the base Shape
class
but it is completely unnecessary in the example I provided.
#include <vector>
#include <variant>
template<class... Ts> struct overload : Ts... { using Ts::operator()...; };
template<class... Ts> overload(Ts...) -> overload<Ts...>;
struct Shape {
virtual ~Shape() = default;
};
struct Square : public Shape {};
struct Triangle : public Shape {};
struct Circle : public Shape {};
template<typename T>
struct RenderCommand {
void render(const T&) {}
};
/* Your RenderCommand specializations here. */
using Shapes = std::variant<Square, Triangle, Circle>;
int main()
{
std::vector<Shapes> shapes {Square{}, Triangle{}, Circle{}};
for (const auto& shape : shapes)
{
std::visit(overload{
[](const Square& s) { RenderCommand<Square>{}.render(s); },
[](const Triangle& t) { RenderCommand<Triangle>{}.render(t); },
[](const Circle& c) { RenderCommand<Circle>{}.render(c); }
}, shape);
}
}
CodePudding user response:
I went ahead and decided to build something similar to the first suggestion given by WBuck. One piece of information that I forgot to include in the original answer is that RenderCommand<Shape>
also inherits from an abstract base class BaseRenderCommand
with a pure virtual execute()
function.
The solution I decided upon looks something like this:
RenderCommands.h
struct BaseRenderCommand
{
virtual execute() = 0;
...
}
template<class Shape>
struct RenderCommand : public BaseRenderCommand
{
RenderCommand(Shape *shape) {...};
virtual execute();
...
}
RenderCommands.cpp
#include "RenderCommands.h"
#include "Shapes.h"
void RenderCommand<Triangle>::execute()
{
...
}
void RenderCommand<Circle>::execute()
{
...
}
void RenderCommand<Rectangle>::execute()
{
...
}
Shapes.h
#include RenderCommands.h
struct BaseShape
{
virtual BaseRenderCommand *create_cmd() = 0;
...
}
struct Triangle : public BaseShape
{
virtual BaseRenderCommand *create_cmd();
...
}
struct Circle : public BaseShape
{
virtual BaseRenderCommand *create_cmd();
...
}
struct Rectangle : public BaseShape
{
virtual BaseRenderCommand *create_cmd();
...
}
Shapes.cpp
#include "Shapes.h"
BaseRenderCommand *Triangle::create_cmd()
{
return RenderCommand<Triangle>(this);
}
BaseRenderCommand *Circle::create_cmd()
{
return RenderCommand<Circle>(this);
}
BaseRenderCommand *Rectangle::create_cmd()
{
return RenderCommand<Rectangle>(this);
}
main.cpp
#include <vector>
#include "RenderCommands.h"
#include "Shapes.h"
std::vector<BaseShape*> shapes = new std::vector<BaseShape*>();
std::vector<BaseRenderCommand*> renderCmds = new std::vector<BaseRenderCommand*>();
...
for (auto shape : shapes)
{
renderCmds.push_back(shape->create_cmd())
}