Reputation: 1006
I recently found an example of using static member functions
in pure abstract classes
to initialize pointers to objects of its derived classes.
I was wondering, if it's a design pattern or if it's a good programming practice? The code below is only an ilustration (e.g. both the defineRectangle() and defineCircle()
member
functions):
class Shape;
typedef std::unique_ptr<Shape> shape_ptr;
class Shape{
public:
Shape(){};
virtual ~Shape(){};
virtual void draw() const = 0;
virtual float area() const = 0;
virtual shape_ptr clone() const = 0;
virtual shape_ptr create() const = 0;
static shape_ptr defineRectangle(int, int );
static shape_ptr defineCircle(float);
};
shape_ptr Shape::defineRectangle(int height, int width){
shape_ptr ptrRectangle = shape_ptr(new Rectangle(height, width));
return (ptrRectangle);
}
shape_ptr Shape::defineCircle(float radius){
shape_ptr ptrCircle = shape_ptr(new Circle(radius));
return (ptrCircle);
}
The final goal is to define a container of derived classes
. For instance:
std::vector<std::unique_ptr<Shape> > vect;
and then we could add the derived classes in the container by either calling the static member functions of the Shape
class:
vect.push_back(Shape::defineCircle(10));
vect.push_back(Shape::defineRectangle(5, 4));
or directly without any interface:
vect.push_back(std::unique_ptr<Shape>(new Circle(10)));
vect.push_back(std::unique_ptr<Shape>(new Rectangle(5,4)));
Which of both two ways of adding a derived class in a container should be preferred and why?
The full code can be found in the following link.
Any lights on it are really welcomed ;-)
Upvotes: 4
Views: 4486
Reputation: 523334
Since there is std::unique_ptr
, I assume the compiler supports C++11. In that case, let me offer the third option:
vect.emplace_back(new Circle(10));
vect.emplace_back(new Rectangle(5,4));
(About .emplace_back
: push_back vs emplace_back)
With this you don't need to repeat the shape_ptr
, and you don't need to declare a new factory method to Shape
whenever you add a new subclass.
Edit: In C++14, you can use std::make_unique
to get rid of the raw new
call.
vect.emplace_back(std::make_unique<Circle>(10));
vect.emplace_back(std::make_unique<Rectangle>(5, 4));
Upvotes: 1
Reputation: 36852
I'd advise against putting factory methods in the base class because, technically, Shape
knows nothing about Rectangle
or Circle
. If you add a new shape, such as Donut
, then what will you do? Add a new factory method to Shape
? You'll clutter the interface in no time. So, IMO, the second method would be better.
If you want to reduce the verbosity of having to create shape_ptr
a every time, you could always move the factory methods to the appropriate subclass:
class Circle : public Shape
{
// ...
public:
static shape_ptr make(float radius)
{
return shape_ptr(new Circle(radius));
}
};
// ...
vect.push_back(Circle::make(5.0f));
Upvotes: 1
Reputation: 32510
I was wondering, if it's a design pattern or if it's a good programming practice?
Yes, it's a variation on the factory pattern.
Basically put, it allows you to have a single method, that depending on the arguments to that method, will dispatch the dynamic creation of the correct derived object type. This allows you to use the same "factory" function in your code, and if there are any changes or additions to the underlying objects that the factory method creates, you do not have to change the code that is actually calling your "factory" function. Thus it's a form of encapsulation that isolates any changes for object creation to the segment of the code that is behind the "factory", not the code calling the "factory". For instance, using a factory, it's relatively trivial to add new types that the factory-method can create, but none of the previous code that is making a call to the factory has to change. You merely need to create a new derived class for the new object you want to create, and for any new code that desires that new object, you pass the correct new arguments. All the old arguments still work, and there are not changes that need to take place in the code with regards to returned pointer-types, etc.
The reason for using smart-pointers with the factory is to avoid memory leaks that can occur when pointer-ownership is ambiguous. For instance the factory has to return a pointer since it is dyanmically creating the object. The question then becomes who cleans up the pointer in order to avoid either dangling pointers or memory leaks? Smart pointers clear up this ownership problem, and guarantee that memory is not either inadvertently cleaned up when other objects are still pointing to that memory, or that the memory is not simply lost because the last pointer to that memory location goes out of scope without having delete
called on it.
Upvotes: 1