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).
Related
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.
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();
}
I am reading "Effective Modern C++". In the item related to std::unique_ptr it's stated that if the custom deleter is a stateless object, then no size fees occur, but if it's a function pointer or std::function size fee occurs. Could you explain why?
Let's say that we have the following code:
auto deleter_ = [](int *p) { doSth(p); delete p; };
std::unique_ptr<int, decltype(deleter_)> up(new int, deleter_);
To my understanding, the unique_ptr should have an object of type decltype(deleter_) and assign deleter_ to that internal object. But obviously that's not what's happening. Could you explain the mechanism behind this using smallest possible code example?
A unique_ptr must always store its deleter. Now, if the deleter is a class type with no state, then the unique_ptr can make use of empty base optimization so that the deleter does not use any additional space.
How exactly this is done differs between implementations. For instance, both libc++ and MSVC store the managed pointer and the deleter in a compressed pair, which automatically gets you empty base optimization if one of the types involved is an empty class.
From the libc++ link above
template <class _Tp, class _Dp = default_delete<_Tp> >
class _LIBCPP_TYPE_VIS_ONLY unique_ptr
{
public:
typedef _Tp element_type;
typedef _Dp deleter_type;
typedef typename __pointer_type<_Tp, deleter_type>::type pointer;
private:
__compressed_pair<pointer, deleter_type> __ptr_;
libstdc++ stores the two in an std::tuple and some Google searching suggests their tuple implementation employs empty base optimization but I can't find any documentation stating so explicitly.
In any case, this example demonstrates that both libc++ and libstdc++ use EBO to reduce the size of a unique_ptr with an empty deleter.
If the deleter is stateless there's no space required to store it. If the deleter is not stateless then the state needs to be stored in the unique_ptr itself.
std::function and function pointers have information that is only available at runtime and so that must be stored in the object alongside the pointer the object itself. This in turn requires allocating (in the unique_ptr itself) space to store that extra state.
Perhaps understanding the Empty Base Optimization will help you understand how this could be implemented in practice.
The std::is_empty type trait is another possibility of how this could be implemented.
How exactly library writers implement this is obviously up to them and what the standard allows.
From a unique_ptr implementation:
template<class _ElementT, class _DeleterT = std::default_delete<_ElementT>>
class unique_ptr
{
public:
// public interface...
private:
// using empty base class optimization to save space
// making unique_ptr with default_delete the same size as pointer
class _UniquePtrImpl : private deleter_type
{
public:
constexpr _UniquePtrImpl() noexcept = default;
// some other constructors...
deleter_type& _Deleter() noexcept
{ return *this; }
const deleter_type& _Deleter() const noexcept
{ return *this; }
pointer& _Ptr() noexcept
{ return _MyPtr; }
const pointer _Ptr() const noexcept
{ return _MyPtr; }
private:
pointer _MyPtr;
};
_UniquePtrImpl _MyImpl;
};
The _UniquePtrImpl class contains the pointer and derives from the deleter_type.
If the deleter happens to be stateless, the base class can be optimized so that it takes no bytes for itself. Then the whole unique_ptr can be the same size as the contained pointer - that is: the same size as an ordinary pointer.
In fact there will be a size penalty for lambdas that are not stateless, i.e., lambdas that capture one or more values.
But for non-capturing lambdas, there are two key facts to notice:
The type of the lambda is unique and known only to the compiler.
Non-capturing lambdas are stateless.
Therefore, the compiler is able to invoke the lambda purely based on its type, which is recorded as part of the type of the unique_ptr; no extra runtime information is required.
This is in fact why non-capturing lambdas are stateless. In terms of the size penalty question, there is of course nothing special about non-capturing lambdas compared to any other stateless deletion functor type.
Note that std::function is not stateless, which is why the same reasoning does not apply to it.
Finally, note that although stateless objects are typically required to have nonzero size in order to ensure that they have unique addresses, stateless base classes are not required to add to the total size of the derived type; this is called the empty base optimization. Thus unique_ptr can be implemented (as in Bo Perrson's answer) as a type that derives from the deleter type, which, if it's stateless, will not contribute a size penalty. (This may in fact be the only way to correctly implement unique_ptr without a size penalty for stateless deleters, but I'm not sure.)
From http://en.cppreference.com/w/cpp/memory/unique_ptr:
If T is derived class (sic) of some base B, then std::unique_ptr<T> is
implicitly convertible to std::unique_ptr<B>. The default deleter of
the resulting std::unique_ptr<B> will use operator delete for B,
leading to undefined behavior unless the destructor of B is virtual.
Note that std::shared_ptr behaves differently: std::shared_ptr<B> will
use the operator delete for the type T and the owned object will be
deleted correctly even if the destructor of B is not virtual.
What is the rationale for the difference in behavior upon destruction that is described above? My initial guess would be performance?
Also interesting to know is how an std::shared_ptr<B> is able to call the destructor of a type T in case the destructor on B is non-virtual and can not be called as far as I can see from the context of std::shared_ptr<B>?
std::shared_ptr<X> already has a bunch of overhead over a raw B*.
A shared_ptr<X> basically maintains 4 things. It maintains a pointer-to-B, it maintains two reference counts (a "hard" reference count, and a "soft" one for weak_ptr), and it maintains a cleanup function.
The cleanup function is why shared_ptr<X> behaves differently. When you create a shared_ptr<X>, a function that calls that particular type's destructor is created and stored in the cleanup function managed by the shared_ptr<X>.
When you change types managed (B* becomes C*), the cleanup function remains unchanged.
Because shared_ptr<X> needs to manage the reference counts, the extra overhead of that cleanup function storage is marginal.
For a unique_ptr<B>, the class is almost as cheap as a raw B*. It maintains zero state other than its B*, and its behavior (at destruction) boils down to if (b) delete b;. (Yes, that if (b) is redundant, but an optimizer can figure that out).
In order to support cast-to-base and delete-as-derived, extra state would have to be stored that remembers the unique_ptr is really to a derived class. This could be in the form of a stored pointer-to-deleter, like a shared_ptr.
That would, however, double the size of a unique_ptr<B>, or require it to store data on the heap somewhere.
It was decided that unique_ptr<B> should be zero-overhead, and as such it doesn't support cast-to-base while still calling base's destructor.
Now, you can probably teach unique_ptr<B> to do this by simply adding a deleter type and storing a destruction function that knows the type of thing it is destroying. The above has been talking about the default deleter of unique_ptr, which is stateless and trivial.
struct deleter {
void* state;
void(*f)(void*);
void operator()(void*)const{if (f) f(state);}
deleter(deleter const&)=default;
deleter(deleter&&o):deleter(o) { o.state = nullptr; o.f=nullptr; }
deleter()=delete;
template<class T>
deleter(T*t):
state(t),
f([](void*p){delete static_cast<T*>(p);})
{}
};
template<class T>
using smart_unique_ptr = std::unique_ptr<T, deleter>;
template<class T, class...Args>
smart_unique_ptr<T> make_smart_unique( Args&&... args ) {
T* t = new T(std::forward<Args>(args)...);
return { t, t };
}
live example, where I generate a unique-ptr to derived, store it in a unique-ptr to base, and then reset base. The derived pointer is deleted.
( A simple void(*)(void*) deleter might run into problems whereby the passed in void* would differ in value between the base and derived cases. )
Note that changing the pointer stored in such a unique_ptr without changing the deleter will result in ill advised behavior.
I work with a codebase that was partially implemented by someone who was in love with overly complex solutions to simple problems (e.g. template classes with two parameters that were only ever instantiated for one pair of types). One thing she did was to create objects in a smart pointer, and then have the object store a weak pointer to itself.
class MyClass {
//...
boost::weak_ptr<MyClass> m_self;
//...
};
boost::shared_ptr<MyClass>
Factory::Factory::Factory::CreateMyClass() {
boost::shared_ptr<MyClass> obj(new MyClass(...));
boost::weak_ptr<MyClass> p(obj);
obj->storeSelfPointer(p);
return obj;
}
The class then proceeds to use m_self by locking it and passing around the resulting shared pointer.
For the life of me, I cannot fathom what she was trying to accomplish. Is there some pattern or idea that would explain this implementation? It looks to me like this is completely pointless and I'd like to refactor it away.
EDIT: I should mention that none of the places that use the resulting smart pointer obtained from locking m_self actually retain the smart pointer.
A possible use of this "design" could be to use m_self.lock() to generate shared pointers from this.
If you remove this weak pointer member, the reference count hold by the generated shared pointer from this would be incorrect.
It achieves the same than std::enable_shared_from_this, interestingly enough, cppreference.com mentions this design :
A common implementation for enable_shared_from_this is to hold a weak
reference (such as std::weak_ptr) to this. The constructors of
std::shared_ptr detect the presence of an enable_shared_from_this base
and assign the newly created std::shared_ptr to the internally stored
weak reference
And the C++ standard, section § 20.8.2.4 10 , mention the same possible implementation :
The shared_ptr constructors that create unique pointers can detect the
presence of an enable_shared_- from_this base and assign the newly
created shared_ptr to its
__weak_this member
Possible Refactoring :
If you are using C++11, you can remove the std::weak_ptr member, and publicly inherits from std::enable_shared_from_this<T>. You should retrieve a shared pointer from this by calling shared_from_this().
If you are not using C++11 but can use boost, use boost::enable_shared_from_this, see the boost documentation. You should retrieve a shared pointer from this by calling shared_from_this().
If you are not using C++11, and can't use boost, you can bring the proposed implementation of the standard to your code base, it is short enough :
Code : (copied from § 20.8.2.4 - 11, remove leading underscores, and you probably want to rename it)
template<class T> class enable_shared_from_this {
private:
weak_ptr<T> __weak_this;
protected:
constexpr enable_shared_from_this() : __weak_this() { }
enable_shared_from_this(enable_shared_from_this const &) { }
enable_shared_from_this& operator=(enable_shared_from_this const &) { return *this; }
~enable_shared_from_this() { }
public:
shared_ptr<T> shared_from_this() { return shared_ptr<T>(__weak_this); }
shared_ptr<T const> shared_from_this() const { return shared_ptr<T const>(__weak_this); }
};
And use shared_from_this() to make a shared pointer. If you do copy this code, note that constructing shared pointers from this by other means would not work. The shared pointers constructors need to be modified (as explain by the standard quote above).