Why does unique_ptr take two template parameters when shared_ptr only takes one?
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.
I guess this is the reason: unique_ptr
is supposed to be very lightweight object with zero overhead. Storing deleters with each unique_ptr
could double their size. Because of that people would use good old raw pointers instead, which would be wrong.
On the other hand, shared_ptr
is not that lightweight, since it needs to store reference count, so storing a custom deleter too looks like good trade off.
Why does unique_ptr have the deleter as a type parameter while shared_ptr doesn't?
Part of the reason is that shared_ptr
needs an explicit control block anyway for the ref count and sticking a deleter in isn't that big a deal on top. unique_ptr
however doesn't require any additional overhead, and adding it would be unpopular- it's supposed to be a zero-overhead class. unique_ptr
is supposed to be static.
You can always add your own type erasure on top if you want that behaviour- for example, you can have unique_ptr<T, std::function<void(T*)>>
, something that I have done in the past.
Combing two factory methods returning unique_ptr and shared_ptr into one in C++?
You could use SFINAE, but then I don't really see the point to have this inside a function anymore. It's pretty redundant.
#include <memory>
#include <type_traits>
template <class T, class... Args>
typename std::enable_if<
std::is_same<T, std::shared_ptr<typename T::element_type>>::value, T>::type
createObject(Args&&... args) {
// running some code
return std::make_shared<typename T::element_type>(std::forward<Args>(args)...);
}
template <class T, class... Args>
typename std::enable_if<
std::is_same<T, std::unique_ptr<typename T::element_type>>::value, T>::type
createObject(Args&&... args) {
// running some code
return std::make_unique<typename T::element_type>(std::forward<Args>(args)...);
}
int main() {
auto s = createObject<std::shared_ptr<int>>(1);
auto u = createObject<std::unique_ptr<int>>(1);
}
A little bit more compact but essentially the same idea with a scoped enum
#include <memory>
enum class ptr_t { shared, unique };
template <ptr_t P, class T, class... Args>
typename std::enable_if<P == ptr_t::shared, std::shared_ptr<T>>::type
createObject(Args&&... args) {
// running some code
return std::make_shared<T>(std::forward<Args>(args)...);
}
template <ptr_t P, class T, class... Args>
typename std::enable_if<P == ptr_t::unique, std::unique_ptr<T>>::type
createObject(Args&&... args) {
// running some code
return std::make_unique<T>(std::forward<Args>(args)...);
}
int main() {
auto s = createObject<ptr_t::shared, int>(1);
auto u = createObject<ptr_t::unique, int>(1);
}
In C++17 you of course use if constexpr
in both cases rather than SFINAE.
#include <memory>
enum class ptr_t { shared, unique };
template <ptr_t P, class T, class... Args>
decltype(auto) createObject(Args &&... args) {
// running some code
if constexpr (P == ptr_t::shared) {
return std::make_shared<T>(std::forward<Args>(args)...);
} else if (P == ptr_t::unique) {
return std::make_unique<T>(std::forward<Args>(args)...);
}
}
Deleter type in unique_ptr vs. shared_ptr
Here you can find the original proposal for smart pointers: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2003/n1450.html
It answers your question quite precisely:
Since the deleter is not part of the type, changing the allocation strategy does not break source or binary compatibility, and does not require a client recompilation.
This is also useful because gives the clients of std::shared_ptr
some more flexibility, for example shared_ptr
instances with different deleters can be stored in the same container.
Also, because the shared_ptr
implementations needs a shared memory block anyhow (for storing the reference count) and because there alreay has to be some overhead compared to raw pointers, adding a type-erased deleter is not much of a big deal here.
unique_ptr
on the other hand are inteded to have no overhead at all and every instance has to embed its deleter, so making it a part of the type is the natural thing to do.
Differences between unique_ptr and shared_ptr
Both of these classes are smart pointers, which means that they automatically (in most cases) will deallocate the object that they point at when that object can no longer be referenced. The difference between the two is how many different pointers of each type can refer to a resource.
When using unique_ptr
, there can be at most one unique_ptr
pointing at any one resource. When that unique_ptr
is destroyed, the resource is automatically reclaimed. Because there can only be one unique_ptr
to any resource, any attempt to make a copy of a unique_ptr
will cause a compile-time error. For example, this code is illegal:
unique_ptr<T> myPtr(new T); // Okay
unique_ptr<T> myOtherPtr = myPtr; // Error: Can't copy unique_ptr
However, unique_ptr
can be moved using the new move semantics:
unique_ptr<T> myPtr(new T); // Okay
unique_ptr<T> myOtherPtr = std::move(myPtr); // Okay, resource now stored in myOtherPtr
Similarly, you can do something like this:
unique_ptr<T> MyFunction() {
unique_ptr<T> myPtr(/* ... */);
/* ... */
return myPtr;
}
This idiom means "I'm returning a managed resource to you. If you don't explicitly capture the return value, then the resource will be cleaned up. If you do, then you now have exclusive ownership of that resource." In this way, you can think of unique_ptr
as a safer, better replacement for auto_ptr
.
shared_ptr
, on the other hand, allows for multiple pointers to point at a given resource. When the very last shared_ptr
to a resource is destroyed, the resource will be deallocated. For example, this code is perfectly legal:
shared_ptr<T> myPtr(new T); // Okay
shared_ptr<T> myOtherPtr = myPtr; // Sure! Now have two pointers to the resource.
Internally, shared_ptr
uses reference counting to track how many pointers refer to a resource, so you need to be careful not to introduce any reference cycles.
In short:
- Use
unique_ptr
when you want a single pointer to an object that will be reclaimed when that single pointer is destroyed. - Use
shared_ptr
when you want multiple pointers to the same resource.
How can unique_ptr have no overhead if it needs to store the deleter?
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).
How to feed a shared_ptr to a template function that shall add various data types to a vector?
You can't "move" the thing inside a shared_ptr
into an instance of a unique_ptr
. If you could, all kinds of problems would occur as a result of two different smart pointers trying to delete the thing inside it when they get released.
Instead, just delcare your vector as :
std::vector<std::shared_ptr<segment>> v3;
And then, you really don't need move semantics after that:
template<typename T1, typename T2>
inline void add_segment(T1 & v, T2& line_or_circle)
{
v.emplace_back(line_or_circle);
}
Why do shared_ptr deleters have to be CopyConstructible?
This question was perplexing enough that I emailed Peter Dimov (implementer of boost::shared_ptr
and involved in standardization of std::shared_ptr
)
Here's the gist of what he said (reprinted with his permission):
My guess is that the Deleter had to be CopyConstructible really only as a
relic of C++03 where move semantics didn’t exist.
Your guess is correct. When
shared_ptr
was specified rvalue references
didn't exist yet. Nowadays we should be able to get by with requiring
nothrow move-constructible.There is one subtlety in that when
pi_ = new sp_counted_impl_pd<P, D>(p, d);
throws,
d
must be left intact for the cleanupd(p)
to work, but I
think that this would not be a problem (although I haven't actually
tried to make the implementation move-friendly).
[...]
I think that there will be no problem for the
implementation to define it so that when thenew
throws,d
will be left
in its original state.If we go further and allow
D
to have a throwing move constructor, things get
more complicated. But we won't. :-)
Related Topics
Performance Penalty for Working with Interfaces in C++
Is It Safe to Read an Integer Variable That's Being Concurrently Modified Without Locking
Lru Implementation in Production Code
How to Get the Type of a Lambda Argument
Simple Ipc Between C++ and Python (Cross Platform)
Operator << Must Take Exactly One Argument
Should C++ Eliminate Header Files
Visual Studio: Link:Fatal Error Lnk1181: Cannot Open Input File
Reinterpret_Cast Between Char* and Std::Uint8_T* - Safe
Missing C++ Header <_Debug> After Updating Osx Command Line Tools 6.3
Is Gcc Std::Unordered_Map Implementation Slow? If So - Why
What's the C++ Suffix for Long Double Literals
Openmp Nested Parallel for Loops VS Inner Parallel For
Interpolate from One Color to Another