Reputation: 22173
I've got the following code currently (not working):
#include <iostream>
#include <vector>
class Circle;
class Rectangle;
class Shape {
private:
Shape() {};
public:
virtual ~Shape() {};
friend class Circle;
friend class Rectangle;
};
class Creator {
public:
virtual ~Creator() {};
virtual Shape* create() = 0;
virtual bool equals(Shape& s) { return false; };
};
class Circle : public Shape {
private:
Circle() : Shape() {};
public:
class CircleCreator : public Creator {
public:
virtual Shape* create() { return new Circle(); };
virtual bool equals(Shape& other_shape) { return false; };
};
};
class Rectangle : public Shape {
private:
Rectangle() : Shape() {};
public:
class RectangleCreator : public Creator {
public:
virtual Shape* create() { return new Rectangle(); };
virtual bool equals(Shape& other_shape) { return false; };
};
};
int main() {
/* First step, build the list */
std::vector<Shape*> shapeList;
std::vector<Shape*>::iterator it;
Rectangle::RectangleCreator rc;
Circle::CircleCreator cc;
Shape* s = cc.create();
Shape* s1 = rc.create();
shapeList.push_back(s);
shapeList.push_back(s1);
/* Second step: check if we've got a shape starting from a creator */
for (it = shapeList.begin(); it != shapeList.end(); ++it) {
if (rc.equals(**it)) {
std::cout << "same shape" << std::endl;
}
}
return 0;
}
My goal is to use a factory pattern and avoid the creation of a new object if in a list I've got already that object. I tried to use a double dispatch pattern but it isn't easy to apply in this case. How can I do?
Edit: Since the code is used in a "critical" path, I want to avoid RTTI like dynamic_cast and so on.
Upvotes: 0
Views: 592
Reputation: 2913
I'm not sure what you're trying to do, but I guess this could point you some direction
enum class Shapes
{
Rectangle,
Circle,
...
};
class Shape
{
private:
Shapes m_shape;
protected:
Shape(Shapes shape)
{
m_shape = shape;
}
public:
Shapes GetShape() { return m_shape; } // this is used to check whether two shapes are equal
virtual ~Shape() = default;
};
And now for factory pattern you'd do:
class ShapeFactory
{
public:
static Shape* CreateShape(Shapes shape)
{
switch (shape)
{
case Shapes::Circle:
return new Circle();
// etc.
}
}
};
This feels very redundant and not very clever to me. Also, this can put alot of code into one place.
For the dispatch, you could do (I assume, I'm not really a fan of this concept as it can be made less verbose with a simple template use)
class ShapeCreator
{
public:
virtual Shape* Create() = 0;
virtual ~ShapeCreator() = default;
};
class Circle : public Shape
{
public:
class Creator : ShapeCreator
{
public:
Shape* Create() { return new Circle(); }
};
Circle() : Shape(Shapes::Circle)
{}
};
bool SomethingWithCircle()
{
Circle::Creator circleCreator;
Shape* first = circleCreator.Create();
Shape* second = circleCreator.Create();
// notice memleak here
return first->GetShape() == second->GetShape();
}
If using C++11, you can go even further and avoid the whole idea /which feels very java-like to me anyway/ using a proper template masturbation techniques. (Can still be applied to pre-C++11, you just won't be able specify the parameters.)
template<class T>
class ShapeCreator
{
public:
template<class... TParams>
static T* Create(TParams&&... parameters) { return new T(std::forward<TParams>(parameters)...); }
};
class Rectangle : public Shape
{
private:
int m_width;
int m_height;
public:
Rectangle(int width, int height) : Shape(Shapes::Rectangle)
{
m_width = width;
m_height = height;
}
};
bool DoSomethingWithRectangles()
{
Rectangle* first = ShapeCreator<Rectangle>::Create(10, 15);
Shape* second = ShapeCreator<Rectangle>::Create(20, 25);
// notice memleak here
return first->GetShape() == second->GetShape();
}
TL;DR
You don't really need RTTI but you need to store the type info somewhere in the base type. I'm using the enum Shapes
for this.
Both Factory and Dispatch may seem as a good idea, but you will still need dynamic casting somewhere when using them.
You can replace those two patterns using templates, but as soon as you'll get a vector of the base objects, you'll still have to dynamic_cast
at some point.
I didn't measure this whatsoever, but I'm really interested in performance comparison of using virtual functions and dynamic cast as I imagine they'd be very similar...
End note:
Please notice, that I personally feel that using methods like equals
or operator==
on classes defining the basic interface is not very wise, since there are two possible outcomes:
equals
is virtual -> slow but acceptableequals
is not virtual -> cannot be used in inherited types to actually do more advanced/relevant comparison, breaking the idea of Open to extension, closed for modification
Obviously, if you don't define the equals
, you'd have to write comparison code every time. Or possibly use some templated Comparison
class with possible specializations through traits yielding again the best performance with no code duplicity.
Generally speaking, you can get to point where you'd ask yourself "why isn't there base object and reflection like in java or c# in c++? It would allow me to use all these nice and clever patterns." The answer is templates. Why do it run-time, when you can do it compile time?
Upvotes: 0
Reputation: 44284
Maybe something like this could do it using member variables
#include <iostream>
#include <vector>
enum
{
CIRCLE,
RECTANGLE
};
class Circle;
class Rectangle;
class Shape {
private:
Shape() {};
public:
unsigned shapeType;
virtual ~Shape() {};
friend class Circle;
friend class Rectangle;
};
class Creator {
public:
unsigned shapeType;
virtual ~Creator() {};
virtual Shape* create() = 0;
bool equals(Shape& s) { return (this->shapeType == s.shapeType); };
};
class Circle : public Shape {
private:
Circle() : Shape() {shapeType=CIRCLE;};
public:
class CircleCreator : public Creator {
public:
CircleCreator() {shapeType=CIRCLE;};
virtual Shape* create() { return new Circle(); };
};
};
class Rectangle : public Shape {
private:
Rectangle() : Shape() {shapeType=RECTANGLE;};
public:
class RectangleCreator : public Creator {
public:
RectangleCreator() {shapeType=RECTANGLE;};
virtual Shape* create() { return new Rectangle(); };
};
};
int main() {
/* First step, build the list */
std::vector<Shape*> shapeList;
std::vector<Shape*>::iterator it;
Rectangle::RectangleCreator rc;
Circle::CircleCreator cc;
Shape* s = cc.create();
Shape* s1 = rc.create();
shapeList.push_back(s);
shapeList.push_back(s1);
/* Second step: check if we've got a shape starting from a creator */
for (it = shapeList.begin(); it != shapeList.end(); ++it) {
if (rc.equals(**it)) {
std::cout << "same shape" << std::endl;
}
}
return 0;
}
or this - using virtual function to return type
#include <iostream>
#include <vector>
enum
{
CIRCLE,
RECTANGLE,
UNKNOWN
};
class Circle;
class Rectangle;
class Shape {
private:
Shape() {};
public:
virtual ~Shape() {};
friend class Circle;
friend class Rectangle;
virtual unsigned iAmA(){return UNKNOWN;};
};
class Creator {
public:
virtual ~Creator() {};
virtual Shape* create() = 0;
virtual bool equals(Shape& s) { return false; };
};
class Circle : public Shape {
private:
Circle() : Shape() {};
virtual unsigned iAmA(){return CIRCLE;};
public:
class CircleCreator : public Creator {
public:
CircleCreator() {};
virtual Shape* create() { return new Circle(); };
virtual bool equals(Shape& other_shape) { return (CIRCLE == other_shape.iAmA()); };
};
};
class Rectangle : public Shape {
private:
Rectangle() : Shape() {};
virtual unsigned iAmA(){return RECTANGLE;};
public:
class RectangleCreator : public Creator {
public:
RectangleCreator() {};
virtual Shape* create() { return new Rectangle(); };
virtual bool equals(Shape& other_shape) { return (RECTANGLE == other_shape.iAmA()); };
};
};
int main() {
/* First step, build the list */
std::vector<Shape*> shapeList;
std::vector<Shape*>::iterator it;
Rectangle::RectangleCreator rc;
Circle::CircleCreator cc;
Shape* s = cc.create();
Shape* s1 = rc.create();
shapeList.push_back(s);
shapeList.push_back(s1);
/* Second step: check if we've got a shape starting from a creator */
for (it = shapeList.begin(); it != shapeList.end(); ++it) {
if (rc.equals(**it)) {
std::cout << "same shape" << std::endl;
}
}
return 0;
}
Upvotes: 1