Wrap dynamic array with shared_ptr by make_shared
Asked Answered
E

5

9

I want to write some bytes to an array. To make use of modern C++ I have decided to use a smart pointer.

#include <memory>
#include <cstdint>

using namespace std;

void writeUint32_t(uint32_t value, unsigned char* p){
    *p     = static_cast<unsigned char>((value >> 24) & 0xFF);
    *(++p) = static_cast<unsigned char>((value >> 16) & 0xFF);
    *(++p) = static_cast<unsigned char>((value >>  8) & 0xFF);
    *(++p) = static_cast<unsigned char>((value      ) & 0xFF);
}

int main(){
    auto buf = make_shared<unsigned char[]>(512);
    uint32_t data = 1234;
    writeUint32_t(data, buf.get() + 8);
}

However, I am receiving the following compilation error:

u.cpp:15:37: error: invalid use of array with unspecified bounds
 writeUint32_t(data, buf.get() + 8);
                                 ^
u.cpp:15:38: error: cannot convert ‘unsigned char (*)[]’ to ‘unsigned char*’ for argument ‘2’ to ‘void writeUint32_t(uint32_t, unsigned char*)’
 writeUint32_t(data, buf.get() + 8);

I am using g++ (Ubuntu 5.4.0-6ubuntu1~16.04.2) 5.4.0 20160609 Is there a way to use smart pointers in such a situation?

Ess answered 25/8, 2016 at 10:7 Comment(4)
Works with clang. Livedemo. So what compiler are you using. And I think you cannot use make_shared this way.Sic
g++ (Ubuntu 5.4.0-6ubuntu1~16.04.2) 5.4.0 20160609. For me does not work even with clang version 3.8.0-2ubuntu4.Ess
The problem is the make_shared. I am not 100% sure, but I think you cannot use make_shared to create a pointer to an array. Create a shared_ptr with an array deleter and you are fine. See my Livedemo.Sic
In C++14, shared_ptr does not support array types (nor does make_shared). Support for array types is being added to C++17, though.Shalom
G
8

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.

Gallimaufry answered 25/8, 2016 at 10:15 Comment(4)
Probably the better solution proposed. But IMHO, shared_ptr on vector isn't necessary in many case, according moving semantic. Aslo, to complete, I will rewrite writeUint32_t to use iterator in complement of vectorWrench
@Garf365, you are probably correct on both accounts. But I operated under the assumption that the OP has a set API (that accepts raw pointers), and only their own data is up for modification.Gallimaufry
Except when you don't want the entire vector API, e.g. you explicitly don't want someone to push or pop from the buffer; and you don't really need anything except of size being attached to it.Ion
The problem with this solution is that shared_ptr points to the pointer (std::vector) and in order to access data you need to dereference one more time. Every time when you dereference you access memory in the another CPU cache line (or even worse on the another page). In case if you create shared_ptr for "C-array", the shared_ptr points to data itself.Broncobuster
O
15

Don't use std::make_shared with raw array, it won't construct an array as you expected, but will try to create a pointer to the type you specified, i.e. unsigned char[]. That's why you get a unsigned char (*)[] when use get(), as the error message said.

And std::shared_ptr will delete the pointer by delete by default, not delete[], which should be used for array. You need to specify a customized deleter for it, but std::make_shared does not allow you to specify it.

You could (1) Initialize std::shared_ptr directly and specify the deleter, like

std::shared_ptr<unsigned char> buf(new unsigned char[512], [](unsigned char* p)
{
    delete[] p;
}); 

(2) Use std::unique_ptr instead, which provides a specified version for array, including call delete[] when deallocating and providing operator[] (std::shared_ptr will support it from C++17).

auto buf = std::make_unique<unsigned char[]>(512);

(3) Consider about std::vector<unsigned char> and std::array<unsigned char>.

Overtrick answered 25/8, 2016 at 10:23 Comment(3)
std::vector<unsigned char> adds another level of indirection and std::array<unsigned char> requires that the size be known at compile time. Neither is equivalent to the desired std::shared_ptr<char[]>.Calendre
Is there ever a situation in industry practice where you would prefer to have a shared_ptr to a dynamic array vs just using a vector>Acromion
If you need a shared_ptr, not unique_ptr, combine (1) and (2) like this: auto temp = std::make_unique<unsigned char[]>(512); std::shared_ptr<unsigned char> buf(nullptr, [](unsigned char* p) { delete[] p; }); buf.reset(temp.release()); This way you have the full RAII allocation safety.Superposition
G
8

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.

Gallimaufry answered 25/8, 2016 at 10:15 Comment(4)
Probably the better solution proposed. But IMHO, shared_ptr on vector isn't necessary in many case, according moving semantic. Aslo, to complete, I will rewrite writeUint32_t to use iterator in complement of vectorWrench
@Garf365, you are probably correct on both accounts. But I operated under the assumption that the OP has a set API (that accepts raw pointers), and only their own data is up for modification.Gallimaufry
Except when you don't want the entire vector API, e.g. you explicitly don't want someone to push or pop from the buffer; and you don't really need anything except of size being attached to it.Ion
The problem with this solution is that shared_ptr points to the pointer (std::vector) and in order to access data you need to dereference one more time. Every time when you dereference you access memory in the another CPU cache line (or even worse on the another page). In case if you create shared_ptr for "C-array", the shared_ptr points to data itself.Broncobuster
M
7

If you really want to use a shared_ptr with an array for this (rather than a vector as suggested by StoryTeller), your type should be unsigned char rather than unsigned char[]. To make sure the array is correctly deleted, you need to specify an array deleter to pass to the shared_ptr constructor (so you can't use make_shared as this doesn't allow you to specify the deleter):

auto buf = std::shared_ptr<unsigned char>(new unsigned char[512], std::default_delete<unsigned char[]>());
Mile answered 25/8, 2016 at 10:24 Comment(0)
P
5

You cannot use:

std::make_shared<unsigned char[]>(512);

That because there is no template specialization for Type[]. That because actually std::shared_ptr<T> does not support operator[] yet. It will in the new standard C++17:

operator[] (C++17)


A solution it could be using a STL container, or IMO better: std::unique_ptr.

Indeed, std::unique_ptr supports operator[].

Since C++14, you can:

auto buffer = std::make_unique<unsigned char[]>(512);
buffer[index];

It's exception-safe, almost 0 overhead, and it can be use as a array buffer.

Moreover the deallocation of the buffer is correctly handled by the correct call of delete[] operator.

Personalty answered 25/8, 2016 at 10:30 Comment(0)
B
0

In case if you really need shared_ptr<T[]> and not unique_ptr<T[]>, I would suggest to use boost::shared_ptr. It supports T[].

Pros:

  1. C++20 still does not support shared_ptr<T[]>. Tried g++ 11.3 and VS2022.
  2. Creating the array with the std::shared_ptr<T>(new T[size], deleter) expression is not efficient, as memory for the reference counter is allocated elsewhere.
  3. std::shared_ptr<std::vector> is not efficient either, as shared_ptr points to the pointer. In order to access the data you need to dereference one more time.

boost::shared_ptr solves all the problems:

  1. The memory for the data and the reference counter is allocated in one shot and is allocated nearby which improves the data locality
  2. The shared_ptr points to data itself rather than to another pointer as in case of std::vector

Following is a snippet:

#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>

int main()
{
   auto arr = boost::make_shared<unsigned char[]>(512);
}
Broncobuster answered 19/6, 2023 at 10:12 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.