У меня проблема с разработкой класса, который позволит мне рисовать объекты различной формы.
Shape
учебный классvector<Shape*> ShapeCollection
который хранит производные объекты, т.е. Triangle,Square, Rectangle
В этот момент я застрял в том, каким должен быть дизайн класса, где один класс «Drawing» будет рисовать, потребляя объект класса «Shape». Так как вектор будет содержать разные объекты одного базового класса Shape
, Поскольку у меня есть поток, который выбирает объект из вектора, и как только у меня есть объект, я должен быть в состоянии правильно нарисовать его.
Так что более или менее ниже, это то, что я говорю
class Drawing
{
public:
void Draw(Shape* shape, string objectName)
{
// Now draw the object.
// But I need to know which Object I am drawing or use
// switch statements to identify somehow which object I have
// And then draw. I know this is very BAD!!!
// e.g.
switch(objectName)
{
case "rectangle":
DrawRectangle((Rectangle*) shape)
break;
//Rest of cases follow
}
}
}
Где у меня будет DrawSquare, функция DrawTriangle, которая будет рисовать.
Это должно быть что-то, что было решено. Там должен быть лучший способ сделать это как
все это заявление о переключении должно как-то уйти!
Любое руководство высоко ценится.
Спасибо
@Adrian и @Jerry предложили использовать виртуальную функцию, я думал об этом, но мне нужно, чтобы мой рисунок был удален от базового класса Shape
Вы бы использовали полиморфизм.
void DrawShape() = 0;
)Таким образом, вы можете просто позвонить DrawShape()
на каждом из этих объектов, даже если он передается как объект Shape.
Альтернативы (ПРИМЕЧАНИЕ: код не был проверен):
Указатель на функцию, похожий на создание собственного делегата vtable.
struct square
{
void (*draw)(square&);
};
void drawSquare(square& obj)
{
// draw square code
// there is no 'this'. must access members via `obj`.
}
square s;
s.draw = drawSquare;
s.draw(s);
Функтор, который является классом, который переопределяет operator (), а также как делегат
struct square
{
// Note that std::function can hold a function pointer as well as a functor.
function<void(square&)> draw;
};
struct drawSquare
{
void oparator()(square& obj)
{
// draw square code
// there is no 'this'. must access members via `obj`.
}
};
square s;
square s.draw = drawSquare();
s.draw(s);
ПРИМЕЧАНИЕ: 1 и 2 также могут быть инициализированы лямбда-функциями:
square s;
s.draw = [](square& obj) {
// draw square code
// there is no 'this'. must access members via `obj`.
};
s.draw(s);
ПРИМЕЧАНИЕ: 1 может быть сделано с шаблоном:
struct square;
template <void (*DRAW)(square&)>
struct square
{
void draw()
{
DRAW(*this);
}
};
void drawSquare(square& obj)
{
// draw square code
// there is no 'this'. must access members via `obj`.
}
square s<&drawSquare>;
s.draw();
ПРИМЕЧАНИЕ: 2 можно сделать и с шаблоном:
template <typename DRAW>
struct square
{
void draw()
{
// First set of parentheses instantiate the DRAW object.
// The second calls the functor.
DRAW()(*this);
}
};
struct drawSquare
{
void oparator()(square& obj)
{
// draw square code
// there is no 'this'. must access members via `obj`.
}
};
square s<drawSquare>;
s.draw();
Или в качестве альтернативы, что позволило бы передать функтор с состоянием:
template <typename DRAW>
struct square
{
DRAW draw;
};
struct drawSquare
{
void operator()(square& obj)
{
// draw square code
// there is no 'this'. must access members via `obj`.
}
};
square s<drawSquare>;
s.draw = drawSquare();
s.draw(s);
Унаследуйте от другого класса, который реализует функцию, которую вы хотите либо с шаблонным базовым классом (IIRC, это было сделано в ATL). Это просто свертывание вашего собственного жестко запрограммированного vtable и называется Curly Recurring Type Pattern (CRTP).
template <class D>
struct shape
{
inline void draw() { return static_cast<D&>(*this).draw(); }
};
void draw(square& obj)
{
// draw square code
// No 'this' available. must access shape members via `obj`.
}
struct square : public D<square>
{
void draw()
{
drawSquare(*this);
}
};
Иметь свой draw
класс наследуется от type of shape
класс, который наследуется от базы shape
учебный класс.
struct shape
{
virtual void draw() = 0;
};
struct square : public shape
{
};
struct drawSquare : public square
{
virtual void draw()
{
// draw square code
// you access the square's public or protected members from here
}
};
Использовать std::unordered_map
#include <unordered_map>
#include <typeinfo>
#include <functional>
struct shape { };
struct square : public shape { };
void drawSquare(shape& o)
{
// this will throw an exception if dynamic cast fails, but should
// never fail if called from function void draw(shape& obj).
square& obj = dynamic_cast<square&>(o);
// draw square code
// must access shape members via `obj`.
}
std::unordered_map<size_t, std::function<void(shape&)>> draw_map
{
{ type_id(square).hash(), drawSquare }
};
void draw(shape& obj)
{
// This requires the RTTI (Run-time type information) to be available.
auto it = draw_map.find(type_id(obj).hash());
if (it == draw_map.end())
throw std::exception(); // throw some exception
(*it)(obj);
}
ПРИМЕЧАНИЕ: если вы используете g ++ 4.7, имейте в виду unordered_map
было показано, чтобы иметь проблемы с производительностью.
Это в значительной степени Классическая демонстрация того, когда вы хотите виртуальную функцию. Определить draw
в вашем базовом классе, затем переопределите его в каждом производном классе. Затем, чтобы нарисовать все объекты, вы проходите через коллекцию и вызываете draw()
член для каждого.
class shape {
// ...
virtual void draw(canvas &c) = 0;
};
class square : public shape {
int x, y, size;
// ...
virtual void draw(canvas &c) {
c.move_to(x, y);
c.draw_to(x+size, y);
c.draw_to(x+size, y+size);
c.draw_to(x, y+size);
c.draw_to(x, y);
}
};
…и так далее для каждого типа фигуры, который вам небезразличен.
Редактировать: используя класс стратегии, вы получите код, смутно подобный следующему:
template <class draw>
class shape {
// ...
virtual void draw(canvas &c) = 0;
};
template <class d>
class square : public shape<d> {
// ...
virtual void draw(canvas &c) {
d.square(x, y, size, c);
}
};
Другой возможностью будет использование шаблона посетителя. Это типично используется, когда вам нужно / нужно пересечь более сложную структуру вместо простой линейной последовательности, но также может использоваться и здесь. Это достаточно сложно, так что, возможно, здесь будет немного сложнее, но если вы ищете «Шаблон посетителя», вы должны найти изрядное количество материала.