Can you allocate an array with something equivalent to make_shared?
Asked Answered
L

4

44
buffer = new char[64];
buffer = std::make_shared<char>(char[64]); ???

Can you allocate memory to an array using make_shared<>()?

I could do: buffer = std::make_shared<char>( new char[64] );

But that still involves calling new, it's to my understanding make_shared is safer and more efficient.

Lancey answered 10/12, 2012 at 3:5 Comment(2)
std::vector<char> buffer(64);Desperate
No, you can't do "std::make_shared<char>( new char[64] );".Mineral
C
30

The point of make_shared is to incorporate the managed object into the control block of the shared pointer,

Since you're dealing with C++11, perhaps using a C++11 array would satisfy your goals?

#include <memory>
#include <array>
int main()
{
    auto buffer = std::make_shared<std::array<char, 64>>();
}

Note that you can't use a shared pointer the same way as a pointer you'd get from new[], because std::shared_ptr (unlike std::unique_ptr, for example) does not provide operator[]. You'd have to dereference it: (*buffer)[n] = 'a';

Colossians answered 10/12, 2012 at 3:16 Comment(9)
I can't specify the size of the array at compile time though?Lancey
@JoshElias You sure can at compile time. Do you mean runtime? That would require your own class that takes array size as a constructor argument, since make_shared<T> forwards its runtime arguments to the constructor of T. Or just make a shared vector.Colossians
Yes sorry I did mean runtime. Ah..That's an excellent idea, thank you for educating a noob!Lancey
Upvoted for insight, but you should know that make_shared isn't just for premature optimization; it's also for exception-safety. std::shared_ptr<T>(new T) is a red flag because it leaks the new T if shared_ptr's constructor happens to throw a bad_alloc exception.Consonant
@Consonant there's no leak. The object is deleted if an exception (e.g. bad_alloc) is thrown.Widower
@Widower we're both right, in that the problem case is subtler than my terse comment had implied. The problem is with an expression such as foo(sh_ptr(new T), sh_ptr(new U)); — where there's no sequence point between the calls to sh_ptr's constructor, so a valid order of construction is new U, new T, sh_ptr(<ptr to T>), sh_ptr(<ptr to U>). If either of the middle two calls throw, then new U is leaked. herbsutter.com/gotw/_102Consonant
I have a question about the memory release for useing a shared pointer points to a std::array. Shared pointer is not good to delete arrays. we have to define the deleter to make it works correctly. For the code in your reply, if the shared pointer is out of scope, it will automatically delete all memory of std::array<char, 64> of just the first element in std::array<char, 64>.Aquavit
So you are telling me that if I want a dynamic array of float indeed to: Create my class that inherits from std::array and put that as a parameter for the shred ptr? I'd rather take my chances with float*Tincher
@Tincher no, a dynamic array of float is called vector<float>. Shared pointers aren't related or relevant.Colossians
S
36

Do you need the allocated memory to be shared? You can use a std::unique_ptr instead and the std::make_unique available on C++14:

auto buffer = std::make_unique<char[]>(64);

There will be a std::make_shared version available in C++20:

auto buffer = std::make_shared<char[]>(64);
Scrummage answered 9/11, 2017 at 12:45 Comment(5)
even if you do want it shared, make_unique will work std::shared_ptr<char[]> b; b = std::make_unique<char[]>(25);Gunmaker
@Gunmaker Yes, make_unique will work just fine. But initializing a shared_ptr that way will cost you an extra memory allocation. Which can be avoided when using make_shared.Eunaeunice
@PaulGroke True, but for completeness sake I'll add that that saved allocation comes with a slight risk weak_ptr, make_shared and memory deallocationGunmaker
@Gunmaker Is it fine to remove the semicolon and have it all in one statement? The comment here seems to indicate that it is fine, but wanted to confirm.Mourn
yes, it can be written as std::shared_ptr<char[]> b = std::make_unique<char[]>(25);Gunmaker
C
30

The point of make_shared is to incorporate the managed object into the control block of the shared pointer,

Since you're dealing with C++11, perhaps using a C++11 array would satisfy your goals?

#include <memory>
#include <array>
int main()
{
    auto buffer = std::make_shared<std::array<char, 64>>();
}

Note that you can't use a shared pointer the same way as a pointer you'd get from new[], because std::shared_ptr (unlike std::unique_ptr, for example) does not provide operator[]. You'd have to dereference it: (*buffer)[n] = 'a';

Colossians answered 10/12, 2012 at 3:16 Comment(9)
I can't specify the size of the array at compile time though?Lancey
@JoshElias You sure can at compile time. Do you mean runtime? That would require your own class that takes array size as a constructor argument, since make_shared<T> forwards its runtime arguments to the constructor of T. Or just make a shared vector.Colossians
Yes sorry I did mean runtime. Ah..That's an excellent idea, thank you for educating a noob!Lancey
Upvoted for insight, but you should know that make_shared isn't just for premature optimization; it's also for exception-safety. std::shared_ptr<T>(new T) is a red flag because it leaks the new T if shared_ptr's constructor happens to throw a bad_alloc exception.Consonant
@Consonant there's no leak. The object is deleted if an exception (e.g. bad_alloc) is thrown.Widower
@Widower we're both right, in that the problem case is subtler than my terse comment had implied. The problem is with an expression such as foo(sh_ptr(new T), sh_ptr(new U)); — where there's no sequence point between the calls to sh_ptr's constructor, so a valid order of construction is new U, new T, sh_ptr(<ptr to T>), sh_ptr(<ptr to U>). If either of the middle two calls throw, then new U is leaked. herbsutter.com/gotw/_102Consonant
I have a question about the memory release for useing a shared pointer points to a std::array. Shared pointer is not good to delete arrays. we have to define the deleter to make it works correctly. For the code in your reply, if the shared pointer is out of scope, it will automatically delete all memory of std::array<char, 64> of just the first element in std::array<char, 64>.Aquavit
So you are telling me that if I want a dynamic array of float indeed to: Create my class that inherits from std::array and put that as a parameter for the shred ptr? I'd rather take my chances with float*Tincher
@Tincher no, a dynamic array of float is called vector<float>. Shared pointers aren't related or relevant.Colossians
C
7

How about this?

template<typename T>
inline std::shared_ptr<T> MakeArray(int size)
{
    return std::shared_ptr<T>( new T[size], []( T *p ){ delete [] p; } );
}

auto  buffer = new char[64];
auto  buffer = MakeArray<char>(64);
Comfortable answered 26/5, 2016 at 3:8 Comment(3)
There is a difference between creating new shared_ptr and using make_shared, the later has 1 less atomic operation and it's preferred whenever possible.Hyperkeratosis
This method also doesn't allocate the reference count with the array, so it costs two calls into the memory manager.Whitcomb
Considering C++20 is around the corner and this "polyfill" allows us to write code very close to what it will look like once your compiler of choice supports C++20's make_shared<T[]>(size_t), the overhead will be greatly outweighed by more clean robust future-proof code. If you rename the template to something like "make_shared_array" it's obvious what the intent was when you come back to your code next year to upgrade it ;-) This helps greatly working with variable structure buffers, as many system calls require. The other variable array methods generate blocking cast warnings=errors.Airs
K
1

The most efficient way is to use the make_shared() overload below, which is available in Boost and C++20.

template< class T >
shared_ptr<T> make_shared( std::size_t N );
Kenny answered 10/1, 2022 at 19:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.