Search code examples
c++c++11smart-pointersflexible-array-member

C++ object containing an array of char using unique_ptr


I am looking on a way to use unique_ptr to allocate a structure that contains an array of char with a number of bytes that set dynamically to support different types of message.

Assuming:

struct MyMessage
{
    uint32_t      id;
    uint32_t      data_size;
    char          data[4];
};

How can I convert send_message() below to use a smart pointer?

void send_message(void* data, const size_t data_size)
{
    const auto message_size = sizeof(MyMessage) - 4 + data_size;
    const auto msg = reinterpret_cast<MyMessage*>(new char[message_size]);

    msg->id = 3;
    msg->data_size = data_size;
    memcpy(msg->data, data, data_size);

    // Sending the message
    // ...

    delete[] msg;
}

My attempt to use smart point using the code below does not compile:

const auto message_size = sizeof(MyMessage) - 4 + data_size;
const auto msg = std::unique_ptr<MyMessage*>(new char[message_size]);

Below a complete working example:

#include <iostream>
#include <iterator>
#include <memory>

using namespace std;

struct MyMessage
{
    uint32_t      id;
    uint32_t      data_size;
    char          data[4];
};

void send_message(void* data, const size_t data_size)
{
    const auto message_size = sizeof(MyMessage) - 4 + data_size;
    const auto msg = reinterpret_cast<MyMessage*>(new char[message_size]);
    if (msg == nullptr)
    {
        throw std::domain_error("Not enough memory to allocate space for the message to sent");
    }
    msg->id = 3;
    msg->data_size = data_size;
    memcpy(msg->data, data, data_size);

    // Sending the message
    // ...

    delete[] msg;
}

struct MyData
{
    int  page_id;
    char point_name[8];
};

void main()
{
    try
    {
        MyData data{};
        data.page_id = 7;
        strcpy_s(data.point_name, sizeof(data.point_name), "ab332");
        send_message(&data, sizeof(data));
    }
    catch (std::exception& e)
    {
        std::cout << "Error: " << e.what() << std::endl;
    }
}

Solution

  • The data type that you pass to delete[] needs to match what new[] returns. In your example, you are new[]ing a char[] array, but are then delete[]ing a MyMessage object instead. That will not work.

    The simple fix would be to change this line:

    delete[] msg;
    

    To this instead:

    delete[] reinterpret_cast<char*>(msg);
    

    However, You should use a smart pointer to manage the memory deletion for you. But, the pointer that you give to std::unique_ptr needs to match the template parameter that you specify. In your example, you are declaring a std::unique_ptr whose template parameter is MyMessage*, so the constructor is expecting a MyMessage**, but you are passing it a char* instead.

    Try this instead:

    // if this struct is being sent externally, consider
    // setting its alignment to 1 byte, and setting the
    // size of the data[] member to 1 instead of 4...
    struct MyMessage
    {
        uint32_t      id;
        uint32_t      data_size;
        char          data[4];
    };
    
    void send_message(void* data, const size_t data_size)
    {
        const auto message_size = offsetof(MyMessage, data) + data_size;
    
        std::unique_ptr<char[]> buffer = std::make_unique<char[]>(message_size);
        MyMessage *msg = reinterpret_cast<MyMessage*>(buffer.get());    
    
        msg->id = 3;
        msg->data_size = data_size;
        std::memcpy(msg->data, data, data_size);
    
        // Sending the message
        // ...
    }
    

    Or this:

    using MyMessage_ptr = std::unique_ptr<MyMessage, void(*)(MyMessage*)>;
    
    void send_message(void* data, const size_t data_size)
    {
        const auto message_size = offsetof(MyMessage, data) + data_size;
    
        MyMessage_ptr msg(
            reinterpret_cast<MyMessage*>(new char[message_size]),
            [](MyMessage *m){ delete[] reinterpret_cast<char*>(m); }
        );
    
        msg->id = 3;
        msg->data_size = data_size;
        std::memcpy(msg->data, data, data_size);
    
        // Sending the message
        // ...
    }