What's the Point of Std::Unique_Ptr::Get

what's the point of std::unique_ptr::get

std::unique_ptr provides unique ownership semantics safely. However that doesn't rule out the need for non-owning pointers. std::shared_ptr has a non-owning counterpart, std::weak_ptr. Raw pointers operate as std::unique_ptr's non-owning counterpart.

What's the point of unique_ptr?

As you mentioned, virtual classes are one use case. Beyond that, here are two others:

  1. Optional instances of objects. My class may delay instantiating an instance of the object. To do so, I need to use memory allocation but still want the benefits of RAII.

  2. Integrating with C libraries or other libraries that love returning naked pointers. For example, OpenSSL returns pointers from many (poorly documented) methods, some of which you need to cleanup. Having a non-copyable pointer container is perfect for this case, since I can protect it as soon as it is returned.

What's with unique_ptr::get() instead of &*?

&*b is not necessarily equivalent to b.get(). In order to be sure to recieve a pointer to the managed object, you want to use get().
There are two reasons:

  • Calling operator* on a unique_ptr not managing any object is undefined behaviour
  • The managed object of type T may overload operator& and as such you may not recieve a valid pointer to T.

The standard defines the bevaiour of operator* of std::unique_ptr:

typename add_lvalue_reference<T>::type operator*() const;

  1. Requires: get() != nullptr

  2. Returns: *get()

Thus, applying operator* (from &*) is not allowed if get() returns nullptr.

You will get the return value of any overloaded operator& in the managed object if present. This can be circumvented by using std::addressof but the undefined behaviour problem persists.

std::unique_ptr<T>::get() will give you the address of the managed object or nullptr if no object is managed. It is safer to use get() and you have a predictable result.

Are there any advantages of using std::unique_ptr<T>& instead of std::unique_ptr<T>?

std::unique_ptr can only be moved so if you pass unique_ptr by value then you cannot extract its contents after function call but if you pass by reference then value can be retrieved. Below is sample code for the same :

#include <iostream>
#include <memory>
void changeUniquePtrReference(std::unique_ptr<int>& upr)
{
*upr = 9;
}
void changeUniquePtrValue(std::unique_ptr<int> upv)
{
*upv = 10;
}
int main()
{
std::unique_ptr<int> p(new int);
*p =8;
std::cout<<"value of p is "<<*p<<std::endl;
changeUniquePtrReference(p);
std::cout<<"value of p is "<<*p<<std::endl;
changeUniquePtrValue(std::move(p));
std::cout<<"Memory deallocated so below line will crash.. "<<std::endl;
std::cout<<"value of p is "<<*p<<std::endl;

return 0;
}

Doesn't get() break the idea behind std::unique_ptr?

While Smart pointers manage the lifetime of an object being pointed to, it is often still useful to have access to the underlying raw pointer.

In fact if we read Herb Sutter's GotW #91 Solution: Smart Pointer Parameters he recommends passing parameters by pointer or reference when the function is agnostic to the lifetime of the parameter, he says:

Pass by * or & to accept a widget independently of how the caller is
managing its lifetime. Most of the time, we don’t want to commit to a
lifetime policy in the parameter type, such as requiring the object be
held by a specific smart pointer, because this is usually needlessly
restrictive.

and we should pass by unique_ptr when the function is a sink:

Passing a unique_ptr by value is only possible by moving the object
and its unique ownership from the caller to the callee. Any function
like (c) takes ownership of the object away from the caller, and
either destroys it or moves it onward to somewhere else.

and finally pass a unique_ptr by reference when we can potentially modify it to refer to a different object:

This should only be used to accept an in/out unique_ptr, when the
function is supposed to actually accept an existing unique_ptr and
potentially modify it to refer to a different object. It is a bad way
to just accept a widget, because it is restricted to a particular
lifetime strategy in the caller.

Of course, we are required to get the underlying pointer if we have to interface with C libraries that take pointers.

In your specific example:

int* myPtr = intPtr.get();

There is no transfer of ownership to another smart pointer so there are no problems as long as you don't attempt to delete the pointer via myPtr. You can transfer ownership to another unique_ptr by moving it:

std::unique_ptr<int> intPtr2( std::move( intPtr ) ) ;

why the raw pointer get by std::unique_ptr's get() can not delete the object and how is that implemented

std::unique_ptr is a really simple class. Conceptually, it's basically just this:

template <typename T>
class unique_ptr
{
private:
T* ptr;

public:
unique_ptr(T* p) ptr{p} {}
~unique_ptr() { delete ptr; }

T* get() { return ptr; }
T* release() {
T* p = ptr;
ptr = nullptr;
return p;
}

// other stuff
};

It just has a pointer member, and deletes the pointed-to object in its destructor. In reality there's a bit more to it, but that's essentially it.

The get member just returns a copy of the unique_ptr's managed pointer. That's it. Since the unique_ptr's pointer member is still pointing to that object, its destructor will still delete the object. If you also delete that object via another pointer to it then it will get deleted twice, which results in undefined behavior.

The release member function, on the other hand, sets the unique_ptr's pointer member to nullptr before returning a copy of its original value. Since its member pointer is null, its destructor won't delete anything.

What is the benefit of std::unique_ptr including the deleter as part of the type

The general objective of unique_ptr is to provide automatic deletion of a pointer when the unique-pointer goes out of scope. When the default deleter is used (just calls delete), there is no need for any extra data member in a unique_ptr object except for the pointer itself. This means that by default, unique_ptr has virtually no overhead whatsoever (since most, if not all, of its functions will be inlined).

But they also wanted to be able to provide the option to change the deleter, for the special circumstances where it makes sense. The only way to provide that option, while still being able to optimize it away (storage and inlining the calls) is to make it a static part of the type itself, i.e., through a template parameter. The point is that unique_ptr is intended to be a minimal overhead alternative for a raw pointer.

In the case of shared_ptr, the objective is quite different and the existing overhead is too. A shared-pointer actually uses a shared storage (dynamically allocated) where it stores the pointer, the reference count and the deleter object. In other words, there is already significant overhead and an appropriate place to put the deleter object without causing extra per-pointer overhead. Furthermore, with all the reference counting mechanisms, the overhead of a virtual call (to perform the deletion) pales in comparison to the existing overhead. That's why it was a natural choice to include that convenient feature of type-erasing the deleter object in the shared-pointers.

And, if you want to create a unique-pointer type that has a type-erased deleter, it is quite simple to do with a template alias:

template <typename T>
using any_unique_ptr = std::unique_ptr< T, std::function< void(T*) > >;

Or something similar along those lines, like with this deleter:

template <typename T>
struct type_erased_delete {
std::function< void(T*) > f;

type_erased_delete() : f(std::default_delete<T>()) { };

template <typename Func>
type_erased_delete(Func&& aF) : f(std::forward<Func>(aF)) { };

void operator()(T* p) const { f(p); };
};

template <typename T>
using any_unique_ptr = std::unique_ptr< T, type_erased_delete<T> >;

Pointing to the content of std::unique_ptr

If you can guarantee that A) bar's lifetime will exceed the lifetime of ptr, AND B) that no programmer/refactoring will ever write delete ptr; at any point, then this is perfectly fine and is probably ideal for any situation where you need to pass pointers without ownership.

If those two conditions cannot be guaranteed, you should probably be using std::shared_ptr and std::weak_ptr.



Related Topics



Leave a reply



Submit