Why Isn't There a Std::Shared_Ptr≪T[]≫ Specialisation

Why isn't there a std::shared_ptr T[] specialisation?

The LWG (Library Working Group of the C++ committee) briefly considered the possibility but the idea wasn't without controversy. Though the controversy was mainly about a feature added to the shared_ptr<T[]> proposal that could have been jettisoned (arithmetic on shared_ptr<T[]>).

But ultimately the real real reason is that though it was discussed, there was never an actual written proposal in front of the LWG to do this. It never bubbled up anyone's priority list (including my own) sufficiently to put the time into writing a proposal.

Informal conversations have recently begun anew on this topic among a few LWG members, and I have personally prototyped it. But there is still no written proposal for it. I think it would be a decent additional tool in the toolbox. Whether it will ever actually happen or not is anyone's guess.

Update

Array support for shared_ptr now has a draft TS:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4077.html

Update (2017)

This is now supported in C++17. See case 3 of shared_ptr::shared_ptr()

Why does std::shared_ptr T = std::unique_ptr T[] compile, while std::shared_ptr T[] = std::unique_ptr T[] does not?

§20.8.2.2.1/28:

template <class Y, class D> shared_ptr(unique_ptr<Y, D>&& r); 

Remark: This constructor shall not participate in overload resolution
unless unique_ptr<Y, D>::pointer is convertible to T*.

However, unique_ptr<U[]>::pointer is actually U*, while shared_ptr<U[]>'s T* is U(*)[]; And U* cannot be converted to U(*)[], hence the overload is never considered.

Why is there no [] operator for std::shared_ptr?

std::unique_ptr only defines operator[] in a specialization for arrays: std::unique_ptr<T[]>. For non-array pointers, the operator[] doesn't make much sense anyways (only [0]).

Such a specialization for std::shared_ptr is missing (in C++11), which is discussed in the related question: Why isn't there a std::shared_ptr<T[]> specialisation?

You should not use a non-array smart pointer with array allocation, unless you provide a custom deleter. In particular, unique_ptr<int> p = new int[10] is bad, since it calls delete instead of delete[]. Use unique_ptr<int[]> instead, which calls delete[]. (And this one implements operator[]). If you're using shared_ptr to hold a T[], you need to use a custom deleter. See also shared_ptr to an array : should it be used? -- but it doesn't provide operator[], since it uses type erasure to distinguish between array and non-array (the smart pointer type is independent of the provided deleter).

If you wonder why there is no shared_ptr specialization for arrays: that was a proposal, but wasn't included in the standard (mainly since you can work around by writing ptr.get() + i for ptr[i]).

Problems with shared_ptr T[] wrapping a dynamic array

The solution you suggest is possible, but you will lose the size of the array:

#include <memory>
#include <cstddef>

using namespace std;

template<typename T> shared_ptr<T> make_shared_array(size_t size)
{
return shared_ptr<T>(new T[size], default_delete<T[]>());
}

struct Foo
{
shared_ptr<char> field;
};

int main()
{
Foo a;
a.field = make_shared_array<char>(256);

return 0;
}

What I have done here is to let the array decay into a pointer. As long as the deleter is an array deleter it should behave correctly.

To prevent this loss of size, and if you cannot use boost::shared_array as suggested, I would suggest to encapsulate this information in your own shared_array class.

Conditionally specialize std::hash for std::shared_ptr struct

The problem with the code in your question is not that it is a redefinition, but that default template arguments are not allowed in partial specializations and that all template parameters in a partial specialization must be deducible.

std::hash does not provide a second template parameter in the primary template that could be used for SFINAE, but based on this answer you could do something like this as a workaround:

#include <memory>
#include <utility>

class Base {};

class Derived : public Base {};

template <typename First, typename... Others>
using first = First;

namespace std {

template <typename T>
struct hash<first<std::shared_ptr<T>,
std::enable_if_t<std::is_base_of_v<Base, T>>>> {
size_t operator()(const std::shared_ptr<T>& d) const { return 616; }
};

} // namespace std

which I would assume is ok in principle, because the declaration depends a user-defined type Base.

There is an unresolved issue in the standard regarding whether or not this specialization should be considered a redefinition of the standard's specialization for std::shared_ptr. (GCC thinks it isn't, Clang thinks it is.)

But more importantly, then you still have the problem that this partial specialization is not more specialized than the one that the standard provides for std::shared_ptr. Therefore any actual use would result in an ambiguity error and I don't think there is any way to make the specialization more specialized.

Thus I think your only solution is to define an explicit specialization of std::hash for each derived type, maybe with the help of a macro. Or alternatively (and probably more appropriately) you should write your own hasher functor and provide that as alternative to std::hash where it is needed.

Check for null in std::shared_ptr

Most shared pointers are exactly like normal pointers in this
respect. You have to check for null. Depending on the
function, you may want to switch to using

void myFunction( Foo const& foo );

, and calling it by dereferencing the pointer (which pushes the
responsibility for ensuring that the pointer is not null to the
caller).

Also, it's probably bad practice to make the function take
a shared pointer unless there are some special ownership
semantics involved. If the function is just going to use the
pointer for the duration of the function, neither changing it or
taking ownership, a raw pointer is probably more appropriate,
since it imposes less constraints on the caller. (But this
really depends a lot on what the function does, and why you are
using shared pointers. And of course, the fact that you've
passed a non-const reference to the shared pointer supposes that
you are going to modify it, so passing a shared pointer might be
appropriate.)

Finally, different implementations of shared pointers make it
more or less difficult to check for null. With C++11, you can
use std::shared_ptr, and just compare it to nullptr
naturally, as you'd expect. The Boost implementation is a bit
broken in this respect, however; you cannot just compare it to
0 or NULL. You must either construct an empty
boost::shared_ptr for the comparison, or call get on it and
compare the resulting raw pointer to 0 or NULL.

Wrap dynamic array with shared_ptr by make_shared

I recommend you use std::vector<unsigned char> vec(512);, wrapping contiguous dynamic arrays is exactly what it's for. Getting the raw buffer pointer is as simple as vec.data();

If the vector needs to be shared, than you can still use a smart pointer

auto p_vec = make_shared<vector<unsigned char>>(512);

You'll get the benefit of reference counting with virtually no overhead due to using vector, and you'll get the entire vector API.

Why is `make_unique T[N] ` disallowed?

Quoting from the original proposal:

T[N]

As of N3485, unique_ptr doesn't provide a partial specialization for T[N].
However, users will be strongly tempted to write make_unique<T[N]>(). This
is a no-win scenario. Returning unique_ptr<T[N]> would select the primary
template for single objects, which is bizarre. Returning unique_ptr<T[]>
would be an exception to the otherwise ironclad rule that
make_unique<something>() returns unique_ptr<something>. Therefore, this
proposal makes T[N] ill-formed here, allowing implementations to emit
helpful static_assert messages.

The author of the proposal, Stephan T. Lavavej, illustrates this situation in this video on Core C++ (courtesy of chris), starting from minute 1:01:10 (more or less).

SmartPointers and pointer to array

You are probably asking for

std::shared_ptr<std::array<BYTE,100>> pointer(make_shared<std::array<BYTE,100>>());
LPBYTE old_pointer = pointer.get()->data();

To have another reference you can simply have a statement like this

// increases reference count
std::shared_ptr<std::array<BYTE,100>> pointer2 = pointer;

See the detailed documentations here please: std::shared_ptr constructor and std::shared_ptr::get().

Though remember:

Accessing the smart pointer's pointee using std::shared_ptr::get() bypasses the semantics and features provided by using the std::shared_ptr. That's highly discouraged, unless you very well know what you're doing with old_pointer, and if it's still in scope.



Related Topics



Leave a reply



Submit