5
votes

I wanted to replace some raw pointers in my class with a std::shared_ptr so that I don't have to worry when I create copies of that class. But the raw pointers point to a dynamic array. Using a shared_ptr with dynamic arrays is possible when you give it a custom deleter, e. g. default_delete<T[]>.

But I get a big error list as soon as I try to assign a new value to that field, even on construction.

Here's a minimal code sample:

#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;
  // This line produces the error.
  a.field = make_shared_array<char>(256);

  return 0;
}

NB: Yes, I know that I could/should vector instead of dynamic arrays. But their performance is not the same. I do some heavy image processing and the arrays hold the pixels. On less than VGA resolution the processing time increased from 8 to 11 s. That's quite a lot.


Update: Of course I can provide the errors here. I just didn't know if I should clutter the problem description with it. But here it is:

C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\INCLUDE\memory(754) : error C2664: 'std::_Ptr_base<_Ty>::_Reset0' : cannot convert parameter 1 from 'char ' to 'char ()[]'
with
[
_Ty=char []
]
Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\INCLUDE\memory(723) : see reference to function template instantiation 'void std::shared_ptr<_Ty>::_Resetp0<_Ux>(_Ux *,std::_Ref_count_base *)' being compiled
with
[
_Ty=char [],
_Ux=char
]
C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\INCLUDE\memory(723) : see reference to function template instantiation 'void std::shared_ptr<_Ty>::_Resetp0<_Ux>(_Ux *,std::_Ref_count_base *)' being compiled
with
[
_Ty=char [],
_Ux=char
]
C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\INCLUDE\memory(494) : see reference to function template instantiation 'void std::shared_ptr<_Ty>::_Resetp<_Ux,_Dx>(_Ux *,_Dx)' being compiled
with
[
_Ty=char [],
_Ux=char,
_Dx=std::default_delete
]
C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\INCLUDE\memory(494) : see reference to function template instantiation 'void std::shared_ptr<_Ty>::_Resetp<_Ux,_Dx>(_Ux *,_Dx)' being compiled
with
[
_Ty=char [],
_Ux=char,
_Dx=std::default_delete
]
problem.cpp(9) : see reference to function template instantiation 'std::shared_ptr<_Ty>::shared_ptr>(_Ux *,_Dx)' being compiled
with
[
_Ty=char [],
T=char,
_Ux=char,
_Dx=std::default_delete
]
problem.cpp(9) : see reference to function template instantiation 'std::shared_ptr<_Ty>::shared_ptr>(_Ux *,_Dx)' being compiled
with
[
_Ty=char [],
T=char,
_Ux=char,
_Dx=std::default_delete
]
problem.cpp(21) : see reference to function template instantiation 'std::shared_ptr<_Ty> make_shared_array(size_t)' being compiled
with
[
_Ty=char []
]

4
Would you, perchance, share this "big error list?"Angew is no longer proud of SO
I hear this "their performance is not the same" quite often, can you show some evidence? I have tried quite hard, testing all kinds of operations, but all my results always result in both being of exactly the same speed...PlasmaHH
if using vector::reserve when initializing the vector (or simply initializing it to a fixed size) the performance should be "close enough to same"eladidan
I would expect a std::vector to perform better (unless you make copies).Bo Persson
@primfaktor: That hardly qualifies as evidence, it leaves so much room for other mistakes; e.g. following this verbatim it could even be that foo(T*) got changed to foo(vector<T>)...PlasmaHH

4 Answers

8
votes

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.

4
votes

If you insist that you should not use std::vector, Boost has a boost::shared_array that works as a smart pointer to manage a dynamically allocated array of object.

shared_ptr is not designed to handle an array. Since shared_array is available, why try to use shared_ptr on array s?

3
votes

If you specified the deleter then you don't use T[] in the template argument. Just change T[] to T:

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;
};
3
votes

std::unique_ptr is specialized for array types so you can use T[] with it and it will know that it's still just storing a T*. std::shared_ptr is not specialized this way and so a shared_ptr<T[]> will try to store a pointer to an array, T(*)[], which won't work very well with the conventions around raw arrays in C++. Not to mention that an array of unknown size is an incomplete type, and shared_ptr will eventually need a complete type.

You mention knowing that std::vector should be a better solution but doesn't perform as well. It should perform just fine and you'd probably be better off figuring out why it doesn't.