0

I would like to figure out pro and contra of different ways to deal with the issue of owning and not-owning raw pointers mixed up, within the pre-C++11 OOP framework that I am using. My role "as framework user" is basically to implement a dozen of abstract classes among the huge class hierarchy provided by the framework. This framework (and the "user code examples" from which I start):

  • extensively create objects in the heap via factories/singletons or explicit new, and most of these objects are managed by the framework but others are users' responsibility - and you have to inspect the examples user code and cross-check the thick user manual to be sure which
  • examples suggests to embed other classes in your implementation classes always via pointer (which you create in constructors in the proper way and, if you are the owner, delete in destructor)
  • all interrelation between classes is via pointer, I mean that most of the methods takes non-owning raw pointers as parameter to the objects that they have to use.

In MY user code I want to achieve the following GOALS in order of priority:

  1. Clearly mark which member pointer is an owning pointer, so that I do not have to go through the manual every time that I read again a piece a code
  2. Have a safer code
  3. Have a simpler/more readable/more maintainable code

I consider the following options for all member objects of my implementation classes:

A) all owning raw pointers (clearly detectable by the delete in the destructor but also always cross-checked with the user guide) replaced with unique_ptr.
PRO I achieve all (1), (2) and (3), as e.g. I kick out the trivial destructor, etc
CONTRA I have to add .get() at every call of the framework methods (plus sometimes some .reset() when initlization cannot happen in-class/in-initializer-list), which at least at the first glance looks weird, but maybe one has just to get used to.

B) the "softer" approach of the "Guideline Support Library" owner<T*> "tag".
PRO at least I achieve (1), without diverging too much from the "framework guidelines".
CONTRA I give up with (2) and (3). Further developments will inherit further on this legacy

C) why should I have pointers at all, and not just embed the object inside??? I should get the exact PRO and CONTRA as option (A) - just & to add instead of .get() -, right???
Of course the approach departs more substantially from what the framework suggests.

This is what I have found so far, any further overlooked details (especially warnings) or suggestions are really welcome.

Matteo
  • 47
  • 4
  • I generally write a unique_ptr destructor that calls the owning framework's delete function. I never felt using `.get()` was a chore tbh. – Galik Mar 14 '22 at 08:59

1 Answers1

1

A) all owning raw pointers (clearly detectable by the delete in the destructor but also always cross-checked with the user guide) replaced with unique_ptr.

CONTRA I have to add .get() at every call of the framework methods (plus sometimes some .reset() when initlization cannot happen in-class/in-initializer-list), which at least at the first glance looks weird, but maybe one has just to get used to.

I would definitely recommend this approach. If you are bothered by the get and reset calls, you can define your own wrapper that has implicit conversions. Less safe, still better than the old state. Something like this:

template<class T>
struct owned
{
    std::unique_ptr<T> ptr;

    /*implicit*/ owned(T* ptr=nullptr) noexcept
    : ptr(ptr)
    {}
    /* implicit */ operator T*() const noexcept
    { return ptr.get(); }
};

C) why should I have pointers at all, and not just embed the object inside??? I should get the exact PRO and CONTRA as option (A) - just & to add instead of .get() -, right??? Of course the approach departs more substantially from what the framework suggests

There is a good chance that you can't do this if your framework uses opaque pointers as compilation firewalls.

Another risk is that if you hand out pointers to these objects and then move the objects around, you create dangling pointers. Note that the objects created by the framework might themselves be self-referential or their own constructor/factory might have borrowed references to the object.

A well-written framework should either deal with those cases in copy constructors or deny use of those (making them private if this is pre C++11). That is something you have to check for yourself.

Homer512
  • 9,144
  • 2
  • 8
  • 25
  • Thanks a lot especially for the warnings on option (C). I would like to investigate more on this. As I said, I start with user code examples, and often I see that the example implementation class includes pointers to objects that are initialized in the constructor and explicitly `delete`d in the destructor.
    Isn't this enough to say that this object can be simply embedded in the implementation class, as its lifetime is strictly the same as it, and any attempt to reduce or extend its lifetime would anyway result in a double delete or dangling pointer?
    – Matteo Mar 15 '22 at 08:04
  • However I have to add (just figured out) that embedding an object, instead of having a pointer to it, requires the inclusion of the header file of this object into my class header file, instead of just the forward declaration (allowing to move the inclusion into my class .cpp file). Forward declarations are used extensively in this framework and this might be also the reason why they have chosen to use member pointers – Matteo Mar 15 '22 at 08:11
  • @Matteo, yes pointers avoid having to include the whole definition. That's similar to a compilation firewall. Regarding the first comment: Embedding the object will require it to have a valid move or copy constructor. Those constructors will change the pointer address of the object. That may result in dangling pointers if your framework does not expect it. – Homer512 Mar 15 '22 at 08:20