-1

I'm designing a module to determine if a Point is inside/outside some boundaries defined by geometric objects. In order to do this, I have the following base class :

#pragma once

#include "Point.h"

class IBound
{
    public:
    virtual bool isInside(const Point& point) const = 0;
    virtual ~IBound() {}
};

I want to be able to tell if a point is inside many different boundaries and outside many other boundaries, so I created an object called GeometricBoundaries :

#pragma once

#include "IBound.h"

using BoundariesList = std::vector<IBound*>;

class GeometricBoundaries : public IBound
{
    public:
    GeometricBoundaries(const BoundariesList& boundaries_to_include, const BoundariesList& boundaries_to_exclude)
    : m_boundaries_to_include(boundaries_to_include), m_boundaries_to_exclude(boundaries_to_exclude) {}

    bool isInside(const Point& point) const override;

    /*
        Since we're going to hold on to raw pointers, we don't want an instance of this object to be passed around too much.
        Deleting the copy constructor/assignemnt operator makes sure that this object cannot be copied or moved, 
    */
    GeometricBoundaries(const GeometricBoundaries&) = delete;
    GeometricBoundaries& operator=(const GeometricBoundaries&) = delete;

    private:
    const BoundariesList m_boundaries_to_include;
    const BoundariesList m_boundaries_to_exclude;
};

So, to create a GeometricBoundaries object, I'd need to do something like this :

IBound* square_inc = new Square(...);
IBound* square_exc = new Square(...);
IBound* ellipse_inc = new Ellipse(...);
IBound* circle_exc = new Circle(...);

GeometricBoundaries geometric_boundaries(BoundariesList{square_inc, ellipse_inc}, BoundariesList{square_exc, circle_exc});

Instead of using smart pointers, I decided to use raw pointers. Since I don't really need reference counts and I only want to be able to use polymorphism, I couldn't decide which smart pointer was the "best" in this case and the raw one made more sense. But now, this can lead to some problems where the geometric_boundaries object could live longer than, for example, square_exc or the opposite, both cases would, I think, lead to some memory leaks or undefined behavior.

My intuition tells me I should try to make GeometricBoundaries as "short lived" as possible by deleting the copy/move constructors/assignment operators, but I think this might be the wrong way. I'm still pretty new to C++ and I'd like to know what's the safest way to use such a structure by following the best practices available in CP++11.

IEatBagels
  • 823
  • 2
  • 8
  • 23
  • 2
    I think this is precisely the problem that smart pointers are intended to solve. – Barmar Aug 28 '23 at 20:03
  • 2
    _"Since I don't really need reference counts and I only want to be able to use polymorphism"_ - Then `std::unique_ptr` is for you! – Ted Lyngmo Aug 28 '23 at 20:06
  • Determine who [owns](https://stackoverflow.com/questions/49024982/what-is-ownership-of-resources-or-pointers) the allocated resources and then pick the appropriate smart pointer. If there's one well-defined owner, use `unique_ptr`. If there is more than one relatively-equal partner, use `shared_ptr`. If there is one major partner and one or more lesser partner who only has stake in having different behaviour if a resource has been freed, the major partner holds a `shared_ptr` and the lesser partners hold `weak_ptr`s. – user4581301 Aug 28 '23 at 20:57
  • If you know `geometric_boundaries`'s lifetime is shorter than that of the `IBound`s it contains, the owner won't be freeing them before `geometric_boundaries` can go out of scope, that's a job for `unique_ptr` at the owner and raw pointers in `geometric_boundaries`. – user4581301 Aug 28 '23 at 20:57
  • 1
    Side note: If you don't know, it's too soon to write code. Go back to the drawing board and nail down all of the resource lifetimes. – user4581301 Aug 28 '23 at 21:00

1 Answers1

-2

Use shared pointers. https://en.cppreference.com/w/cpp/memory/shared_ptr

std::shared_ptr is a smart pointer that retains shared ownership of an object through a pointer. Several shared_ptr objects may own the same object. The object is destroyed and its memory deallocated when either of the following happens:

the last remaining shared_ptr owning the object is destroyed;
the last remaining shared_ptr owning the object is assigned another pointer via operator= or reset().

So you create it, you pass it around, when there are no more valid refs to it, the object is released. It's all handled automatically for you.

The link I provided has examples, take a look at those.

ventsyv
  • 3,316
  • 3
  • 27
  • 49
  • 2
    That doesn't seem like good advice since _"I don't really need reference counts and I only want to be able to use polymorphism"_. – Ted Lyngmo Aug 28 '23 at 20:09
  • The ref counting is an internal mechanism, it's not something the user is "getting" and he shouldn't concern himself with it. Yes unique pointer might be appropriate, but by the sound of the problem, I just thought shared pointer is more appropriate. – ventsyv Aug 28 '23 at 20:17
  • Using the much more heavy weight `shared_ptr` as a default for smart pointers isn't a good idea. It introduces complexity (and often a performance loss) that in most cases isn't needed. I'd say use `unique_ptr` until there is an actual usecase for selecting `shared_ptr`. – Ted Lyngmo Aug 28 '23 at 20:23
  • 1
    `shared_ptr` has more implications than just the technical ones. Readers will make different assumptions about the code based on the type of pointer used and will introduce bugs based on faulty assumptions. Write code that means what you want it to mean and describes behaviour that you want the program to have. – user4581301 Aug 28 '23 at 20:45
  • Smart pointers were largely developed to address complicated situations with shared object ownership that tend to introduce memory leaks. What OP describes certainly appears to call for shared pointers. – ventsyv Aug 29 '23 at 13:09