Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ design related question

Here is the question's plot: suppose I have some abstract classes for objects, let's call it Object. It's definition would include 2D position and dimensions. Let it also have some virtual void Render(Backend& backend) const = 0 method used for rendering.

Now I specialize my inheritance tree and add Rectangle and Ellipse class. Guess they won't have their own properties, but they will have their own virtual void Render method. Let's say I implemented these methods, so that Render for Rectangle actually draws some rectangle, and the same for ellipse.

Now, I add some object called Plane, which is defined as class Plane : public Rectangle and has a private member of std::vector<Object*> plane_objects;

Right after that I add a method to add some object to my plane.

And here comes the question. If I design this method as void AddObject(Object& object) I would face trouble like I won't be able to call virtual functions, because I would have to do something like plane_objects.push_back(new Object(object)); and this should be push_back(new Rectangle(object)) for rectangles and new Circle(...) for circles.

If I implement this method as void AddObject(Object* object), it looks good, but then somewhere else this means making call like plane.AddObject(new Rectangle(params)); and this is generally a mess because then it's not clear which part of my program should free the allocated memory.

["when destroying the plane? why? are we sure that calls to AddObject were only done as AddObject(new something).]

I guess the problems caused by using the second approach could be solved using smart pointers, but I am sure there have to be something better.

Any ideas?

like image 965
M. Williams Avatar asked Jan 22 '23 04:01

M. Williams


2 Answers

Your actual problem seems to be managing the objects' lifetimes. Four possibilities that come to mind are:

  1. Your container (i.e. Plane) assumes ownership of all contained objects and therefore deletes them once it's itself destroyed.

  2. Your container (Plane) does not assume ownership and whoever added objects to your container will be responsible for destroying them.

  3. The lifetime of your objects is managed automatically.

  4. You circumvent the problem by providing the container with a clone of the actual object. The container manages a copy of the object, and the caller manages the original object.

What you currently have seems like approach #4. By doing:

plane_objects.push_back(new Object(object));

you insert a copy of the object into the container. Therefore the problem sort of disappears. Ask yourself whether this is really what you want, or if one of the above choices would be more suitable.


Options #1 and #2 are easy to implement, because they define a contract that your implementation simply has to follow. Option #3 would call for e.g. smart pointers, or some other solution involving reference counting.

If you want to keep following the approach of option #4, you could e.g. extend your Object class with a clone method, so that it returns the right type of object. This would get rid of the incorrect new Object(...).

class Object
{
    public:
        virtual Object* clone() const = 0;
        ...
};

...

Object* Rectangle::clone() const
{
    return new Rectangle(*this);  // e.g. use copy c'tor to return a clone
}

P.S.: Note how the STL containers seem to deal with this issue: Let's say you declare a vector<Foo>. This vector will contain copies of the objects inserted into it (option #4 in my answer). However, if you declare the collection as vector<Foo*>, it will contain references to the original objects, but it will not manage their lifetime (option #2 in my answer).

like image 106
stakx - no longer contributing Avatar answered Jan 30 '23 06:01

stakx - no longer contributing


Use a smart pointer like boost::shared_ptr or boost::intrusive_ptr.

Why do you assume there is something better than using a smart pointer? Storing raw pointers in a container generally ends in tears if you don't take extraordinary steps to ensure exception safety in your code.

like image 32
James McNellis Avatar answered Jan 30 '23 08:01

James McNellis