2

We have a function that returns a new allocated object as a output argument (ref to pointer).

MyFunc(MyObject*& obj)
{
    obj = new MyObject();
}

Which is called like so:

Object* obj;
MyFunc(obj);

Internally the function does quite a bit and uses shared_ptr for memory management. When it is done, the object we would like to return is referenced by a shared_ptr. I am struggling on how to return our new allocated object as a regular pointer.

We would like to continue to use shared_ptr internally to reduce risks, but it does not seem to make sense to return a shared_ptr as the caller takes complete ownership over the returned value (the called function or object no longer needs or keeps a reference to the returned data) and we want them to have flexibility.

Does anyone have any suggestions for allowing us to use shared_ptr internally but have a regular pointer interface? Thanks

denver
  • 2,863
  • 2
  • 31
  • 45
  • sounds like u should use a unique_ptr instead, then just return .get() – AndersK Feb 14 '17 at 16:16
  • @AndersK. I don't want the caller to be required to use unique_ptr either. – denver Feb 14 '17 at 16:17
  • 3
    Is it impossible to change the interface? Do you need to interface with C code or something? Why can't you just return a smart pointer, as in idiomatic C++? A reference to a pointer is horrible ugliness. – Cody Gray - on strike Feb 14 '17 at 16:17
  • 3
    @denver if you are not sharing the variable then internally use a unique_ptr, once you want to pass the ownership to the caller via just do .release() to get the pointer – AndersK Feb 14 '17 at 16:19
  • @AndersK. unique_ptr and .release would seem to be the ticket in most situations, here however we need to have multiple references internally (hence the shared_ptr). – denver Feb 14 '17 at 16:24
  • @CodyGray These functions are intended to be used by a codebase that already has other memory management strategies in place (proprietary smart pointers) and we want to avoid additional confusion. – denver Feb 14 '17 at 16:26
  • Do you want to keep the internal shared_ptr after you return? Or is the object mananaged outside since then. – EmDroid Feb 14 '17 at 16:28
  • @axalis The object is managed outside. No internal reference is kept or needed. – denver Feb 14 '17 at 16:30
  • 1
    @denver, then please edit your question, because it's nowhere clear what is the intended/wanted/real scope and lifetime of the shered_ptr you are referring to. That may make the difference between a formally correct answer but not appropriate for your scenario, and a correct answer appliable to your case. – roalz Feb 14 '17 at 16:52

6 Answers6

3

It depends on who "owns" the pointer, once it has been exposed to the 'outside world.' Ownership essentially boils down to: "who is responsible for freeing this memory, later?"

It can be answered with a simple question: when MyFunc is called, is the caller responsible for deleting the pointer when it's done?

  1. If so, then MyFunc needs to 'release' the ownership, otherwise the shared_ptr will automatically delete the pointer, when it goes out of scope. This actually can't be done, using shared_ptr. You need to use a unique_ptr instead, and call unique_ptr::release().
  2. If not - if MyFunc will simply use the resulting pointer and forget about it without delete-ing it - then you can simply return the 'raw' pointer using shared_ptr::get(). You must be careful, because this implies that the shared_ptr still exists elsewhere in your code.
BTownTKD
  • 7,911
  • 2
  • 31
  • 47
  • Won't returning shared_ptr::get() return an invalid pointer as the object would have been deleted when MyFunc ended and the shared_ptr went out of scope. – denver Feb 14 '17 at 16:32
  • 1
    Not if the shared_ptr still exists somewhere else. That's why you need to answer the question: who "owns" the pointer? – BTownTKD Feb 14 '17 at 16:33
  • The caller assumes ownwership. – denver Feb 14 '17 at 16:35
  • Then you can't use shared_ptr. You have to use unique_ptr::release(). Or you have to change the API, so that the caller also provides a shared_ptr. Or just use raw pointers with "new." – BTownTKD Feb 14 '17 at 16:36
  • 2
    The OP specifically describe the use of the shared_ptr as "internal to the function", which may mean that its scope is limited to that function, thus it will call the deleter upon function exit, and the raw pointer will be a dangling pointer... boom – roalz Feb 14 '17 at 16:37
  • Uhm, from a more recent comment from the OP, it seems that the shared_ptr is "managed outside", still not clear what its scope and lifetime is... – roalz Feb 14 '17 at 16:54
3

If for some reason you cannot/want not use std::unique_ptr or std::auto_ptr (for example if you need to have multiple owners internally during creation for some reason or your underlying methods require std::shared_ptr to be passed around), you can still make it work with std::shared_ptr by using custom deleter, as described here: https://stackoverflow.com/a/5995770/1274747

In the principle, after you're done before the return, you switch the deleter to not actually delete the instance (make the deleter "null") and then return by shared_ptr get(). Even after all shared_ptr objects are destroyed, the memory will not be deleted (as the nulled deleter will skip the deletion).

There is also a link in the comments not so well visible, which might be of your interest: http://paste.ubuntu.com/23866812/ (not sure though if it would really work without the shared ownership of the switch in all cases, would need to test)


EDIT

As expected, with the linked simple disarmable deleter from the pastebin you need to be careful, because the deleter is actually copied for storing in std::shared_ptr.

But you can still make it work by using std::ref:

MyFunc(MyObject*& obj)
{
    DisarmableDelete<MyObject> deleter;
    std::shared_ptr<MyObject> ptr(new MyObject(), std::ref(deleter));
    // do what is necessary to setup the object - protected by deleter
    // ...
    // disarm before return
    deleter._armed = false;
    obj = ptr.get();
    // deleter disarmed - object not freed
}

And just for completeness (and to avoid a potential future broken link), here is the implementation of DisarmableDelete from http://paste.ubuntu.com/23866812/.

template <typename T, typename Deleter = typename std::default_delete<T> >                                                                                                                              
    struct DisarmableDelete : private Deleter {                                                                                                                                                         
        void operator()(T* ptr) { if(_armed) Deleter::operator()(ptr); }                                                                                                                                
        bool _armed = true;                                                                                                                                                                             
    };        
Community
  • 1
  • 1
EmDroid
  • 5,918
  • 18
  • 18
  • If you using shared_ptr - as the OP states - for "memory management," and then purposefully going out of your way to provide a 'null' deleter, then you're better off just using raw pointers with "new" in the first place. – BTownTKD Feb 14 '17 at 16:39
  • 1
    Yes, but you still might want to auto-delete the object if something bad (e.g. exception) happens before you have everything done. Note that the deleter is not 'null' since the beginning, but you only make it 'null' after everything is fine and you actually want to return (and discard the ownership). – EmDroid Feb 14 '17 at 16:42
  • Thanks for following up with that example. – denver Feb 14 '17 at 19:16
  • Why bother with `std::ref`, just `ptr.get_deleter()._armed = false;`? – Yakk - Adam Nevraumont Feb 14 '17 at 19:56
  • Is the `_armed` accessible via `ptr.get_deleter()`? AFAIK the shared_ptr deleter is type-erased, so accessing `_armed` might involve casting (true, we know the type so we can `static_cast`). – EmDroid Feb 14 '17 at 21:54
2

I can see four alternatives, as highlighted below. They are all horrible, and short of switching your ownership to std::unique_ptr<T> and returning via obj = ptr.release(); I can only offer a hack where the argument is assigned to the pointer upon destruction, but you still need to catch the exception and test whether the pointer was assigned.

#include <iostream>
#include <memory>
#include <exception>

struct foo {
  void bar() const { std::cout << this << " foo::bar()\n"; }
  ~foo() { std::cout << this << " deleted\n"; }
};

void f1(foo*& obj) {
  obj = new foo;
  // do stuff... if an exception is thrown before we return we are
  // left with a memory leak
}

void f2(foo*& obj) {
  auto holder = std::make_shared<foo>();
  // do stuff.. if an exception is thrown the pointer will be
  // correclty deleted.

  obj = holder.get(); // awesome, I have a raw pointer!
} // oops, the destructor gets called because holder went out of
  // scope... my pointer points to a deleted object.

void f3(foo*& obj) {
  auto holder = std::make_unique<foo>();
  // do stuff.. if an exception is thrown the pointer will be
  // correclty deleted.

  obj = holder.release(); // awesome, I have a raw pointer!
} // no problem whem holder goes out of scope because it does not own the pointer

void f4(foo*& obj) {
  // a super-weird hack that assigns obj upon deletion
  std::shared_ptr<foo> holder(new foo, [&obj](foo*& p){  obj = p; });
  throw std::exception();
} // no problem whem holder goes out of scope because it does not own
  // the pointer... but if an execption is throw we need to delete obj

int main() {

  foo* p1;
  f1(p1);
  p1->bar();

  foo* p2;
  f2(p2);
  // p2->bar(); // error

  foo* p3;
  f3(p3);
  p3->bar();

  foo* p4;
  try {
    f4(p4);
  } catch(...) {
    std::cout << "caught an exception... test whether p4 was assigned  it\n";
  }
  p4->bar(); // I still need to delete this thing
}
Escualo
  • 40,844
  • 23
  • 87
  • 135
2

The point of shared_ptr is to express shared ownership.

Anything that does not share in the ownership of an object -- that doesn't have the right to make an object lifetime last longer, and the object lifetime is the union of the shared owners request for object lifetime -- should not use a shared_ptr.

Here, you have a shared_ptr and you are going to return it. At that point, you are violating the assumptions of shared_ptr; anyone who kept a shared_ptr copy expects that its content can last as long as it requests.

Meanwhile, the calling code thinks it owns the MyObject* raw pointer you passed it.

This is an example of misuse of shared_ptr.

Saying "we have memory management issues, use shared_ptr" doesn't fix memory management issues. Proper use of shared_ptr requires care and design, and the design must be that when the end of the lifetime of the object in question is shared by 2 or more pieces of data and/or code.

The internal code, if it does not own the pointer, should use either something like an observer_ptr<T> or a raw T* (the first to make it clear it doesn't own the object).

Ownership should be explicit, and in a unique_ptr. It can then call .release() to pass ownership to a raw pointer if required; in practice, I would change your signature to take a unique_ptr&, or have it return a unique_ptr.

The caller would then call .release() when they want to use some other object lifetime management system, or that object lifetime management system should consume unique_ptrs (thus being extremely clear about taking ownership of things).


Use a non-hack solution.

Such as a std::shared_ptr<std::unique_ptr<T>>. In this case, you have shared ownership of a unique ownership.

The unique_ptr can have its ownership taken from it (via .release()). When it does so, all of the shared_ptrs that still exist will have their unique_ptr also be cleared.

This places the shared unique ownership front and center, instead of hacking a non-deleter into a shared_ptr and having dangling shared_ptrs that think they have ownership over data but do not.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Thanks for the opinion. The questions was about returning an object through an abstracted interface (application calling third party library). There is no memory management issue and no shared ownership of the data across the interface, it is a situation of ownership being transferred. One side is generating the data for the other side to consume. – denver Feb 15 '17 at 14:17
  • This issue was about each side of the interface being able to manage memory in its own way. A unique_ptr rather than a raw pointer would be an appropriate way to convey the relationship, but it is not an appropriate representation of the object's lifetime from the library's perspective, and a change in representation needs to occur to pass back through such an interface. – denver Feb 15 '17 at 14:17
  • @denver The lifetime of an object is either managed or not. If you have two systems both managing the lifetime of the same object, you have a broken design. Unique ptr can actually give the lifetime of an object away (or, with a destroyer, have an arbitrary definition of what end of lifetime means). Shared ptr fundamentally *cannot* do this; it is a type error to try. You can hack it, but that is hacking around the design of shared ptr rather than using it properly. Shared ptr by design does not play well with others; forcing it will result in bugs and code debt. – Yakk - Adam Nevraumont Feb 15 '17 at 14:23
  • The caller cares not where the data came from or how it got created, from its perspective it is just getting it. Similarly, the generator of the data should be able to manage it however is appropriate (in this case some sort of shared representation, as the object is assembled from multiple contributors) while it has ownership of it and not care how the caller manages it. Also, unique_ptr (and other STL types) are generally not safe to pass across a library/application boundary as it may be defined differently on each side of the boundary, specifically if different compilers are used. – denver Feb 15 '17 at 14:54
  • I understand the limitations of using shared_ptr internally from this perspective, and the question was really about how to get around it. In this case, working around the limitation is looking more favorable then rolling out some sort of custom solution. – denver Feb 15 '17 at 15:01
  • @denver No, "manage it however it wants" makes no sense. It needs manage so it can transfer ownership if it needs to transfer ownership. Transferring ownership is obviously not possible in *all* resource management systems (say, stack based). You have asked how to hack ownership transfer into a system that doesn't support ownership transfer. I am stating that your plan is a bad one, and you should solve it without hacking it in, and I pointed out problems with the obvious hacks. If your internal system is using shared ptr as a "generic smart pointer", that is your design problem. – Yakk - Adam Nevraumont Feb 15 '17 at 16:11
  • @denver Added a concrete way to solve your problem without hacking around it. – Yakk - Adam Nevraumont Feb 15 '17 at 16:14
  • std::shared_ptr> is an interesting idea, although it may not be any clearer to code maintainers then the use of a custom deleter. – denver Feb 15 '17 at 16:55
1

The best approach is to use internally unique_ptr and call its .release() method before returning the raw pointer.

If you are stuck to use shared_ptr internally, an option is to create it specifying a custom, "noop" deleter, that just does nothing when shared_ptr is destroyed (instead of calling delete on the owned pointer). The get the raw pointer from shared_ptr as usual (.get() method). An example of such a deleter can be found in the Boost library (null_deleter).
But please note that doing this effectively "disable" the usefulness of having a shared_ptr at all...

roalz
  • 2,699
  • 3
  • 25
  • 42
  • Cool, didn't know about `boost::null_deleter`. I checked it, unfortunately it might not be as useful in this case, as the OP apparently still wants to free the pointer if something happens before return, so what is needed would be a deleter which is not null at the beginning, but can be "made null" right before returning from the function. – EmDroid Feb 14 '17 at 16:48
  • @axalis than not a null_deleter, but the custom deleter approach is still valid, just need a more complex one that can switch between 2 behaviors (deleter => null deleter) – roalz Feb 14 '17 at 16:55
  • @roalz Thanks for helping to drive the discussion. – denver Feb 14 '17 at 19:19
0

If your managing the lifespan of the allocated object internally with std::shared_ptr, and are returning a raw pointer for access and don't want this pointer to affect the ref count, you can return the raw pointer by calling shared_ptr.get().

It can be problematic to return smart pointers if your using a tool like Swig to generate wrappers for other languages.

Dennis Ward
  • 747
  • 3
  • 8
  • 21