Deleters for unique_ptr - c++

There is an array holding unique pointers:
std::array<std::unique_ptr<T, deleter<allocator<T>>>> storage;
where
template<typename ALLOC>
class deleter {
void operator()( void ) { ... }
};
does the deletion as required by unique_ptr. Effectively, it calls the destructor and then deallocates the memory occupied. So far so good.
But there is another deleter:
template<typename T>
class empty_deleter {
void operator()( void ) {}
};
which performs no operation at all - no destruction, no deallocation.
The reason why it exists is to have, in theory, the option to store object owned by such unique_ptr<T, empty_deleter<T>> within the storage array...
The question is - how to achieve that? To make the deleters compatible so that I can assign unique_ptr<T, empty_deleter<T>> to an array of unique_ptr<T, deleter<allocator<T>>> pointers...
I know there are converting constructors within the unique_ptr implementation so in theory, the deleter of one type can be assinged to the unique_ptr being declared with another type but there is a constraint these deleters shall be convertible somehow... could you please advice me how to reach that?
Additionally - once I am successfull in assigning the deleter instance of empty_deleter<T> into unique_ptr<T, deleter<allocator<T>>> whatever how, which operator() is going to be called once the deletion is triggered? The one from empty_deleter<T> or from deleter<allocator<T>>?

You can do this by simply releaseing the pointer from one object and passing it to the other. These two types are incompatible by design. You are not supposed to make them compatible, so you have to do this weird thing to make it work.
Yes, you could make this work by making deleter constructible and assignable from an empty_deleter. But that is bad, because it logically makes no sense.
unique_ptr is supposed to own the object; that's what it is for. You shouldn't want to have a non-owning unique_ptr. If someone gets a unique_ptr, that's supposed to mean they own that thing.
Worse, the very idea of taking a non-owning pointer and claiming ownership of it is highly dubious. If a piece of code does not have the right to delete something (which is what the type unique_ptr<T, empty_deleter<T>> is supposed to mean), it also does not have the right to delegate responsibility for deleting it to someone else.
The code you are trying to write is highly unclear on who owns what (or even what it means to "own" an object) and should be rethought.

As #Nicol Bolas points out, "object owned by such unique_ptr<T, empty_deleter<T>>" is nonsensical. I will answer "how to make a smart pointer that sometimes owns and sometimes doesn't own it's pointee".
None of std::unique_ptr<T, empty_deleter<T>>, std::unique_ptr<T, deleter<allocator<T>>>, nor std::unique_ptr<T, deleter<other_allocator<T>>> are assignable to one another.
If you want to mix and match ownerships of your pointers, you will have to type-erase the deleter. The simplest way is to use the existing function-object type-erasure type, std::function.
template <typename T>
class pmr_unique_ptr : public std::unique_ptr<T, std::function<void(T *)>> {
public:
using unique_ptr::unique_ptr;
// have to supply a deleter
pmr_unique_ptr(pointer) = delete;
pmr_unique_ptr() = delete;
pmr_unique_ptr(std::nullptr_t) = delete;
};
This can be constructed from std::unique_ptr<T, D> so long as D is copyable.

Related

How can unique_ptr have no overhead if it needs to store the deleter?

First take a look at what C++ Primer said about unique_ptr and shared_ptr:
$16.1.6. Efficiency and Flexibility
We can be certain that shared_ptr does not hold the deleter as a direct member, because the type of the deleter isn’t known until run time.
Because the type of the deleter is part of the type of a unique_ptr, the type of the deleter member is known at compile time. The deleter can be stored directly in each unique_ptr object.
So it seems like that the shared_ptr does not have a direct member of deleter, but unique_ptr does. However, the top-voted answer of another question says:
If you provide the deleter as template argument (as in unique_ptr) it is part of the type and you don't need to store anything additional in the objects of this type. If deleter is passed as constructor's argument (as in shared_ptr) you need to store it in the object. This is the cost of additional flexibility, since you can use different deleters for the objects of the same type.
The two quoted paragraph are totally conflicting, which makes me confused. What's more, many people says unique_ptr is zero overhead because it doesn't need to store the deleter as member. However, as we know, unique_ptr has a constructor of unique_ptr<obj,del> p(new obj,fcn), which means that we can pass a deleter to it, so unique_ptr seems to have stored deleter as a member. What a mess!
std::unique_ptr<T> is quite likely to be zero-overhead (with any sane standard-library implementation). std::unique_ptr<T, D>, for an arbitrary D, is not in general zero-overhead.
The reason is simple: Empty-Base Optimisation can be used to eliminate storage of the deleter in case it's an empty (and thus stateless) type (such as std::default_delete instantiations).
The key phrase which seems to confuse you is "The deleter can be stored directly". But there's no point in storing a deleter of type std::default_delete. If you need one, you can just create one as std::default_delete{}.
In general, stateless deleters do not need to be stored, as you can create them on demand.
Angew's answer explained pretty thoroughly what's going on.
For those curious how things could look under the covers
template<typename T, typename D, bool Empty = std::is_empty_v<D>>
class unique_ptr
{
T* ptr;
D d;
// ...
};
template<typename T, typename D>
class unique_ptr<T, D, true> : D
{
T* ptr;
// ...
};
Which specializes for empty deleters and take advantage of empty base optimization.
Brief intro:
unique_ptr can introduce some small overhead, but not because of the deleter, but because when you move from it value must be set to null where if you were using raw pointers you could leave the old pointer in bug prone but legitimate state where it still points to where it pointed before. Obviously smart optimizer can optimize, but it is not guaranteed.
Back to the deleter:
Other answers are correct, but elaborate. So here is the simplified version witout mention of EBO or other complicated terms.
If deleter is empty(has no state) you do not need to keep it inside the unique_ptr. If you need it you can just construct it when you need it. All you need to know is the deleter type(and that is one of the template arguments for unique_ptr).
For exaple consider following code, than also demonstrates simple creation on demand of a stateless object.
#include <iostream>
#include <string>
#include <string_view>
template<typename Person>
struct Greeter{
void greet(){
static_assert(std::is_empty_v<Person>, "Person must be stateless");
Person p; // Stateless Person instance constructed on demand
std::cout << "Hello " << p() << std::endl;
}
// ... and not kept as a member.
};
struct Bjarne{
std::string_view operator()(){
return "Bjarne";
}
};
int main() {
Greeter<Bjarne> hello_bjarne;
hello_bjarne.greet();
}

How to use a shared_ptr with a pointer to struct that should not be freed

Currently I'm using some functions from the glib library. With glib also comes the gio. glib is a C library and therefore I need to delete some structures that I create.
for many of the objects I create a smartpointer eg:
std::shared_ptr<GAsyncQueue> my_queue = std::shared_ptr<GAsyncQueue>(g_async_queue_create(), g_async_queue_unref);
For this creates a shared pointer to an GAsyncQueue and this is safely destroys the queue on its end of its life.
However, I encounter a problem when I obtain a pointer from the gio library that I should not free. In the following code my_connection is a GSocketClient which implements (in glib speak) GIOStream.
std::shared_ptr<GInputStream> my_input_stream =
std::shared_ptr<GInputStream> (
g_io_stream_get_input_stream(G_IO_STREAM(my_connection.get()))
);
Because the documentation on GIOStream mentions, that the pointer obtained with g_io_stream_get_input_stream() should not be freed. That is because it is owned by the my_connection instance.
I thought about creating a lamda for the destroy object, the second parameter of a shared pointer object. eg auto deleter = [](GInputStream* ptr) {}; and then give that lambda as destroy function to the shared pointer, but that feels a kind of stupid.
Well, alternative to no-op deleter might be using aliasing shared pointer
template <class U> shared_ptr (const shared_ptr<U>& x, element_type* p) noexcept;
It shares x, but after get() you'll get back p.
Discussion: What is shared_ptr's aliasing constructor for?
You probably just don't need a std::shared_ptr. And you probably don't even need a pointer.
As I read your question and comments, I don't see any point against
auto& my_input_stream = *( g_io_stream_get_input_stream(G_IO_STREAM(my_connection.get())) )
It is true that pointers allow optional data. However, it's also true that it's mostly used the wrong way. Having
void foo( type* ptr)
{
if (!ptr)
throw exception;
}
often doesn't make sense. If the function has to to work on concrete data, allowing a NULL parameter is only useful if you then worry about providing that data. Otherwise, just require a reference (possibly const) to the object.
Smart pointers are useful; but they're still pointers. Avoiding them altogether, if possible, is even better.
From the comments:
However, a reference must always be initialized
Absolutely. Since C++11 though we've got std::reference_wrapper which can also be reassinged and stored in containers.
You can use a deleter type that does nothing, but it will need to be passed as an argument to the shared_ptr's constructor
struct DoNothing {
template <typename T>
void operator()(T*) const noexcept { }
};
When creating a shared_ptr you will need to create one of these deleters and pass it in the constructor (as you're doing with the lambda). You can make this easier on yourself with an intermediate function
template <typename T>
std::shared_ptr<T> non_deleting_shared_ptr(T* ptr) {
return {ptr, DoNothing};
}
auto my_input_stream =
non_deleting_shared_ptr(
g_io_stream_get_input_stream(G_IO_STREAM(my_connection.get()));
However the bigger question is why you're using smart pointers when you don't want ownership to be a part of it. You'd almost certainly be better off with just a GAsyncQueue*, unless of course you're in a situation where you have a shared_ptr that needs to free sometimes. Like a data member maybe?

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

Is there a reason behind why std::unique_ptr contains the deleter function signature as part of the template definition?
template<class T, class Deleter = std::default_delete<T>> class unique_ptr;
vs
template< class T > class shared_ptr;
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> >;
It means that, if the deleter has no state, the unique_ptr can be no larger than a plain pointer; and moving it no more expensive than copying and nulling a pointer.
To be able to handle multiple types of deleter, as shared_ptr can, it would need to use type erasure. To do that, each unique_ptr would need to contain a pointer to the deleter (to call it polymorphically), as well as a pointer to the managed object, doubling its size.
shared_ptr already has that extra overhead, since it needs a pointer to access the reference count. The deleter can be stored along with the reference count.
In the case of shared_ptr, it uses type-erasure on the deleter. There is some extra overhead in performing that type erasure, which unique_ptr does not want. In particular, most solutions will require allocation of dynamic memory for any function object deleter. Given the goal of unique_ptr, I can understand why they chose not to use that technique.
On the other hand, shared_ptr already includes some extra overhead for the reference counting, as it will already require dynamic allocation for the reference count. This means that the additional overhead for the type-erasure was smaller. That, and that's how boost::shared_ptr was written, and the standardized version was basically a capture of that.
Could be constructed with new[] - Therefore delete[] would need to be called - not delete.
The template gives you this option along with things such as logging etc.

C++ Rule of Zero : polymorphic deletion and unique_ptr behavior

In the recent overload journal under the topic Enforcing the rule of zero, the authors describe how we can avoid writing the Rule of five operators as the reasons for writing them are:
Resource management
Polymorphic deletion
And both these can be taken care of by using smart pointers.
Here I am specifically interested in the second part.
Consider the following code snippet:
class Base
{
public:
virtual void Fun() = 0;
};
class Derived : public Base
{
public:
~Derived()
{
cout << "Derived::~Derived\n";
}
void Fun()
{
cout << "Derived::Fun\n";
}
};
int main()
{
shared_ptr<Base> pB = make_shared<Derived>();
pB->Fun();
}
In this case, as the authors of the article explain, we get polymorphic deletion by using a shared pointer, and this does work.
But if I replace the shared_ptr with a unique_ptr, I am no longer able to observe the polymorphic deletion.
Now my question is, why are these two behaviors different? Why does shared_ptr take care of polymorphic deletion while unique_ptr doesn't?
You have your answer here: https://stackoverflow.com/a/22861890/2007142
Quote:
Once the last referring shared_ptr goes out of scope or is reset, ~Derived() will be called and the memory released. Therefore, you don't need to make ~Base() virtual. unique_ptr<Base> and make_unique<Derived> do not provide this feature, because they don't provide the mechanics of shared_ptr with respect to the deleter, because unique pointer is much simpler and aims for the lowest overhead and thus is not storing the extra function pointer needed for the deleter.
It'll work if you use the C++14 make_unique or write your own one like in Yakk's answer. Basically the difference between the shared pointer behavior is that you got:
template<
class T,
class Deleter = std::default_delete<T>
> class unique_ptr;
for unique_pointer and as you can see, the deleter belongs to the type. If you declare a unique_pointer<Base> it'll always use std::default_delete<Base> as default. But make_unique will take care of using the correct deleter for your class.
When using shared_ptr you got:
template< class Y, class Deleter >
shared_ptr( Y* ptr, Deleter d );
and other overloads as constructor. As you can see the default deleter for unique_ptr depends on the template parameter when declaring the type (unless you use make_unique) whilst for shared_ptr the deleter depends on the type passed to the constructor.
You can see a version that allows polymorphic delete without virtual destructor here (this version should also work in VS2012). Note that it is quite a bit hacked together and I'm currently not sure what the behavior of unique_ptr and make_shared in C++14 will be like, but I hope they'll make this easier. Maybe I'll look into the papers for the C++14 additions and see if something changed if I got the time later.
template<typename T>
using smart_unique_ptr=std::unique_ptr<T,void(*)(void*)>;
template<class T, class...Args> smart_unique_ptr<T> make_smart_unique(Args&&...args) {
return {new T(std::forward<Args>(args)...), [](void*t){delete (T*)t;}};
}
The problem is that the default deleter for unique_ptr calls delete on the stored pointer. The above stores a deleter that knows the type at construction, so when copied to base class unique_ptr will still delete as the child.
This adds modest overhead, as we have to dereference a pointer. In addition it denormalizes the type, as default constructed smart_unique_ptrs are now illegal. You can fix this with some extra work (replace a raw function pointer with a semi smart functor that at least does not crash: the function pointer, however, should be asserted to exist if the unique is non-empty when the deleter is invoked).

Shared pointers and raw pointers in same container

I need to populate container with shared pointers and raw pointers at same time.
I guess shared_ptr<T> may be forced to behave like T*, if constructed with no-op deleter and no-op (de)allocator?
Or may be there is universal smart pointer, which binds to different (smart and raw) pointer types?
Or may be shared_ptr can point to one object, but manage lifetime of another (nullptr) object of same type?
Background. I have simple component system. Some components are built-in, no management required, raw pointers will be enough. Other components are external DLLs, they must be attached when requested and detached when removed from "session". For latter I am using wrapper component, that will detach DLL when destroyed.
EDIT: Background updated.
EDIT2: Question updated.
EDIT3: Found straight solution. See my answer if interested.
Generally speaking, no; a container contains exactly one type of object.
You could use some kind of boost::variant<shared_ptr<T>, T*> object as the contained object. But you'd need visitors to access the elements of it. Alternatively, you can give boost::shared_ptr a special deleter object that doesn't actually delete the pointed-to value.
Well, it really depends on what your needs are, and more specifically what you would like to achieve in terms of ownerships:
Should your vector own the shared_ptr<T> ? (or could you just store the pointee ?)
Should your vector own the T* ?
Then there are varied solutions.
Full ownership: just encapsulate the T* in a regular shared_ptr<T>
Partial ownership: as stated, you can just instantiate a shared_ptr<T> with a no-op deleter to wrap the T*
No ownership: just unwrap the shared_ptr<T> using .get() and only store T*
Shared pointer may point to one object, but manage lifetime of another object. Curiously, it may manage nothing, but still point to something. There is special constructor, that accepts another shared pointer, that used to determine which object to share. We can pass empty shared pointer in it.
template <typename T>
std::shared_ptr<T> fake_shared(T *ptr)
{
std::shared_ptr<T> dummy (nullptr); // we have nothing to manage;
std::shared_ptr<T> faked (dummy, ptr); // but we have something to point to;
return faked;
}
Moreover, we can exploit 2 facts. Firstly, this constructor will accept shared pointer of any (other) type. Secondly, std::shared_ptr<void> is legal, and may be used to express our intent more clearly.
template <typename T>
std::shared_ptr<T> fake_shared(T *ptr)
{
std::shared_ptr<void> dummy (nullptr); // we have really nothing to manage;
std::shared_ptr<T> faked (dummy, ptr); // but we have something to point to;
return faked;
}
Alternatively, one may use constructor, that accepts custom deleter, and pass no-op deleter into it.
template <typename T>
std::shared_ptr<T> fake_shared(T *ptr)
{
std::shared_ptr<T> faked (ptr, [](T *){}); // won't be deleted;
return faked;
}
EDIT: Revision, shared void, custom deleter.