C ++ Design Related Issues

Here's the plot of the question: suppose I have some abstract classes for objects, let's call it Object . This definition will include two-dimensional 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 the Rectangle and Ellipse class. Guess that they will not have their own properties, but they will have their own virtual void Render method. Let's say I implemented these methods, so the Render for the Rectangle actually draws some kind of rectangle the same for the ellipse.

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

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

And here the question arises. If I create this method as void AddObject(Object& object) , I would void AddObject(Object& object) into a problem, since I could not name virtual functions, because I would need to do something like plane_objects.push_back(new Object(object)); , and it 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 nice, but then in another place it means a call like plane.AddObject(new Rectangle(params)); , and this is usually a mess, because then he does not understand which part of my program should free the allocated memory,

["when destroying a plane? why? sure that AddObject calls AddObject executed only as AddObject(new something ).]

I think the problems caused by using the second approach can be solved with smart pointers, but I'm sure there should be something better.

Any ideas?

+4
source share
5 answers

Your actual problem seems to be controlling the life of the objects . Four possibilities that come to mind:

  • Your container (i.e. Plane ) assumes ownership of all contained objects and, therefore, delete them as soon as it is destroyed.

  • Your container ( Plane ) does not accept ownership, and whoever adds objects to your container will be responsible for their destruction.

  • The lifetime of your objects is automatically controlled.

  • You circumvented the problem by giving the container a clone of the real object. The container controls the copy of the object, and the caller controls the original object.

What you now have is similar to approach # 4. Performing:

 plane_objects.push_back(new Object(object)); 

you paste a copy of the object into the container. Therefore, the problem of gender disappears. Ask yourself if this is really what you want, or if one of the above options would be more appropriate.


Options # 1 and # 2 are easy to implement because they define the contract your implementation should follow. Option number 3 will cause, for example, smart pointers or another solution that includes reference counting.

If you want to continue to take approach No. 4 , you can, for example. extend the Object class with the clone method so that it returns the correct type of object. This will save you from the wrong new Object(...) .

 class Object { public: virtual Object* clone() const = 0; ... }; ... Object* Rectangle::clone() const { return new Rectangle(*this); // eg use copy c'tor to return a clone } 

PS: Notice how the STL containers seem to handle this problem: let's say you declare vector<Foo> . This vector will contain copies of the objects inserted into it (option number 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 control their lifetime (option No. 2 in my answer).

+3
source

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

Why are you suggesting that there is something better than using a smart pointer? Storing raw pointers in a container usually ends in tears unless you take emergency steps to ensure that exceptions in your code are safe.

+3
source

You can add the clone method to your interface, for example, clones of instances of your type hierarchy. Each particular implementation creates a new instance of this particular class, so all type information is stored.

 class Object { public: virtual Object clone() = 0; }; class Rectangle : public Object { public: virtual Rectangle clone() { /* create and return an identical Rectangle */ } }; class Ellipse : public Object { public: virtual Ellipse clone() { /* create and return an identical Ellipse */ } }; class Plane : public Rectangle { std::vector<Object*> plane_objects; public: virtual Plane clone() { /* create and return an identical Plane */ } void AddObject(const Object& object) { plane_objects.push_back(object.clone()); } }; 

This means that the plane is the owner of all its objects, so it must also destroy them in its destructor. If these objects are only available domestically, they can be stored with simple pointers, although even then smart pointers make it easy and easy to delete. However, if published, it is best to use smart pointers to store them.

+1
source

when destroying an airplane? What for? we that calls to AddObject were only made as AddObject (new something).

It is impossible to be sure. But something must control the lifetime of the object. You should simply clearly document what is.

+1
source

First you need to find out your object identifier and property semantics:

Is each object a real person or just refers to a real object? For the first, you cannot use clone (), and you need to use reference counting (via boost :: shared_ptr) or pass by reference if the owner of the instance is guaranteed to survive the links to it. For the latter, clone () may make more sense, and you can safely ignore ownership issues.

In general, you should avoid passing bare pointers and use boost :: shared_ptr instead of or links.

+1
source

Source: https://habr.com/ru/post/1307939/


All Articles