Michael Marcin
Michael Marcin

Reputation: 704

shared_ptr to variable length struct

How can I combine the variable length struct idiom

struct Data
{
  std::size_t size;
  char data[];
};

with the make_shared idiom which does essentially the same thing so that I can end up with a shared_ptr to one contiguous block of memory which contains the ref count structure header and structure data.

i.e. something like

// allocate an extra 30 bytes for the data storage
shared_ptr<Data> ptr = allocate_shared<Data>( vls_allocator(30) );

Upvotes: 4

Views: 1355

Answers (2)

Tony Wall
Tony Wall

Reputation: 1412

This is an important requirement in my opinion especially when writing code which must interact with or provide interfaces to low level system functionality (outside the world of modern C++ but still compliant with modern code).

There are two parts to the solution. First allocating the buffer as a smart pointer and secondly casting it to the right type in a safe way which passes the code analysis/guideline checkers.

The second part is generally available with "std::reinterpret_pointer_cast<T>" however this will not accept a cast from any array type without spewing out warnings=errors from the compiler or guideline checkers.

The first part which is the missing link until C++20 is to create a safe shared_ptr to a buffer of variable length, for example "shared_ptr(size_t length)" created by "std::make_shared<T[]>(size_t)" but sadly forgotten in the compiler/standard library versions before C++20 (even though they did implement "make_unique<T>(size_t)" and the request for change for the same in make_shared has been around for years).

I stumbled upon this macro which when combined with std::reinterpret_pointer_cast gives you a nice "polyfill" to achieve the same result in clean code until C++20 is generally available.

// C++20 polyfill for missing "make_shared<T[]>(size_t size)" overload.
template<typename T>
inline std::shared_ptr<T> make_shared_array(size_t bufferSize)
{
    return std::shared_ptr<T>(new T[bufferSize], [](T* memory) { delete[] memory; });
}

// Creates a smart pointer to a type backed by a variable length buffer, e.g. system structures.
template<typename T>
inline std::shared_ptr<T> CreateSharedBuffer(size_t byteSize)
{
    return std::reinterpret_pointer_cast<T>(make_shared_array<uint8_t>(byteSize));
}

Example:

size_t privilegesSize = sizeof(TOKEN_PRIVILEGES) + (sizeof(LUID_AND_ATTRIBUTES) * privilegesCount);
auto tokenPrivileges = CreateSharedBuffer<TOKEN_PRIVILEGES>(privilegesSize);

The only side effect is apparently a second memory access will occur during allocation but given C++20 is around the corner it's probably more efficient and robust for your solution to get running now with clean robust code than worrying about an extra processor cycle or two for what will be a short period of time.

All you have to do when C++20 is available is delete the "polyfill" template and rename or replace the call to it from "make_shared_array" to "make_shared" (with the new C++20 overload). Optionally ditch the whole CreateSharedBuffer macro if the C++20 solution below is acceptable:

auto data = std::reinterpret_cast<Data>(std::make_shared<uint8_t>(sizeof(Data) + (sizeof(char) * 30)));

Upvotes: 1

Michael Anderson
Michael Anderson

Reputation: 73520

You can achieve this using boosts intrusive shared pointers, (Not sure if this is supported directly by C++11).

http://www.boost.org/doc/libs/1_55_0/libs/smart_ptr/intrusive_ptr.html

You'd need to create a larger struct with the reference count in it though and your pointers would point at the laregr structure rather than its contained Data member.

You also need to hook up your deallocation function into to these pointers.

NOTE: I suspect there are ways to get a shared pointer into the Data member, rather than the wrapper - but last time i did that with boost shared_ptr code it required some interesting hacks.

Upvotes: 1

Related Questions