указатель на байт[] в std::unique_ptr не компилируется

#c

#c

Вопрос:

У меня есть тип, который должен быть удален с помощью пользовательского deleter . Для выделения я использую размещение new с помощью byte[] .

Также я хочу вернуть unique_ptr при выделении. К сожалению, выделение не работает.

Я попробовал это:

 std::unique_ptr<CustomType*, CustomTypeDeleter>
allocate_node(const std::stringamp; value, int levels)
{
    // get size of Skip_node
    const auto node_size =
        sizeof(CustomType)   (levels - 1) * sizeof(CustomType*);

    // allocate memory
    std::unique_ptr<std::byte[]> node = std::make_unique<std::byte[]>(node_size);

    // construct object in allocated space
    new (node.get()) CustomType{value, levels, {nullptr}};

    // transform byte[] into std::unique_ptr<Skip_node*, Skip_node_deleter>
    return {reinterpret_cast<CustomType*>(node.release())};  //does not work
}
  

К сожалению, последняя строка не работает. Я бы ожидал, что мы преобразуем byte[] в std::unique_ptr<Skip_node*, Skip_node_deleter> но я получаю эту ошибку компиляции:

 g   -c -pipe -g -std=gnu  1z -Wall -Wextra -fPIC -DQT_QML_DEBUG -I../untitled -I. -I../Qt/5.15.0/gcc_64/mkspecs/linux-g   -o main.o ../untitled/main.cpp
../untitled/main.cpp: In function ‘std::unique_ptr<CustomType*, CustomTypeDeleter> allocate_node(const stringamp;, int)’:
../untitled/main.cpp:38:58: error: could not convert ‘{((CustomType*)node.std::unique_ptr<std::byte []>::release())}’ from ‘<brace-enclosed initializer list>’ to ‘std::unique_ptr<CustomType*, CustomTypeDeleter>’
   38 |     return {reinterpret_cast<CustomType*>(node.release())};  //does not work
      |                                                          ^
      |                                                          |
      |                                                          <brace-enclosed initializer list>
In file included from /usr/include/c  /10.2.0/memory:83,
                 from ../untitled/main.cpp:2:
/usr/include/c  /10.2.0/bits/unique_ptr.h: In instantiation of ‘std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = CustomType*; _Dp = CustomTypeDeleter]’:
../untitled/main.cpp:43:40:   required from here
/usr/include/c  /10.2.0/bits/unique_ptr.h:357:56: error: static assertion failed: unique_ptr's deleter must be invocable with a pointer
  357 |  static_assert(__is_invocable<deleter_typeamp;, pointer>::value,
      |                                                        ^~~~~
/usr/include/c  /10.2.0/bits/unique_ptr.h:361:17: error: no match for call to ‘(std::unique_ptr<CustomType*, CustomTypeDeleter>::deleter_type {aka CustomTypeDeleter}) (std::remove_reference<CustomType**amp;>::type)’
  361 |    get_deleter()(std::move(__ptr));
      |    ~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~
../untitled/main.cpp:13:10: note: candidate: ‘void CustomTypeDeleter::operator()(CustomType*) const13 |     void operator()(CustomType* p) const noexcept
      |          ^~~~~~~~
../untitled/main.cpp:13:33: note:   no known conversion for argument 1 from ‘std::remove_reference<CustomType**amp;>::type’ {aka ‘CustomType**’} to ‘CustomType*’
   13 |     void operator()(CustomType* p) const noexcept
      |                     ~~~~~~~~~~~~^
make: *** [Makefile:725: main.o] Error 1
11:55:23: The process "/usr/bin/make" exited with code 2.
Error while building/deploying project untitled (kit: Desktop Qt 5.15.0 GCC 64bit)
When executing step "Make"
  

Минимальный пример:

 #include <string>
#include <memory>
#include <cstddef>

struct CustomType {
    std::string value;
    int levels;
    CustomType* next[1];
};

class CustomTypeDeleter {
public:
    void operator()(CustomType* p) const noexcept
    {
        if (p) {
            p->~CustomType();

            auto raw_p = reinterpret_cast<std::byte*>(p);
            delete[] raw_p;
        }
    }
};

std::unique_ptr<CustomType*, CustomTypeDeleter>
allocate_node(const std::stringamp; value, int levels)
{
    // get size of Skip_node
    const auto node_size =
        sizeof(CustomType)   (levels - 1) * sizeof(CustomType*);

    // allocate memory
    std::unique_ptr<std::byte[]> node = std::make_unique<std::byte[]>(node_size);

    // construct object in allocated space
    new (node.get()) CustomType{value, levels, {nullptr}};

    // transform byte[] into std::unique_ptr<Skip_node*, Skip_node_deleter>
    return {reinterpret_cast<CustomType*>(node.release())};  //does not work
}

int main()
{
    auto node = allocate_node("test", 3);
}
  

Комментарии:

1. Вы уверены, что хотите a std::unique_ptr<CustomType*, CustomTypeDeleter> , а не a std::unique_ptr<CustomType, CustomTypeDeleter> ?

2. Кроме того, почему вы создаете a std::unique_ptr<std::byte[]> вместо выделения памяти с new помощью or malloc ?

Ответ №1:

Вам нужно вернуть a std::unique_ptr<CustomType, CustomTypeDeleter> (не a std::unique_ptr<CustomType*, CustomTypeDeleter> ), и вам нужен экземпляр deleter:

 std::unique_ptr<CustomType, CustomTypeDeleter>
allocate_node(const std::stringamp; value, int levels)
{
    //...
    return {reinterpret_cast<CustomType*>(node.release()), CustomTypeDeleter{}};
}
  

Полная функция:

 std::unique_ptr<CustomType, CustomTypeDeleter>
allocate_node(const std::stringamp; value, unsigned levels) {
    // get size of Skip_node
    const auto node_size = sizeof(CustomType)   (levels - 1U) * sizeof(CustomType*);

    // allocate memory
    auto raw = new std::byte[node_size];

    // construct object in allocated space
    auto rv = new(raw) CustomType{value, levels}; // use return value from new
    std::fill_n(rv->next, levels - 1U, nullptr); // set all to nullptr, not only one

    return {rv, CustomTypeDeleter{}};
}
  

ДЕМОНСТРАЦИЯ

Просто имейте в виду, что доступ next за пределы границ имеет неопределенное поведение (хотя это, вероятно, сработает).

Кроме того, объекты массива технически не начали свой жизненный цикл, пока вы new их не используете.

Вот альтернатива, которая не приведет к неопределенному поведению программы:

 struct CustomType {
    std::string value;
    unsigned levels;
    CustomType** const next; // not a CustomType*[]
};

template<typename T, typename ArrT>
class CustomTypeDeleter {
public:
    void operator()(T* p) const noexcept {
        if(p) {
            p->~T(); // main object destructor

            // call destructor on an array of objects
            for(size_t i=0; i < p->levels;   i) p->next[i].~ArrT();

            // delete memory
            delete[] reinterpret_cast<std::byte*>(p);            
        }
    }
};

static std::unique_ptr<CustomType, CustomTypeDeleter<CustomType, CustomType*>> 
allocate_node(const std::stringamp; value, unsigned levels) {

    // get size of Skip_node
    auto node_arr_align = std::max(sizeof(CustomType), sizeof(CustomType*));
    auto node_size = node_arr_align   levels * sizeof(CustomType*);

    // allocate memory
    auto raw = new std::byte[node_size];

    // start lifetime of array objects
    auto arr = new(raw   node_arr_align) CustomType*[levels]{};

    // construct object in allocated space
    auto rv = new(raw) CustomType{value, levels, arr}; // use return value from new

    return {rv, CustomTypeDeleter<CustomType, CustomType*>{}}; 
}
  

Комментарии:

1. Спасибо за попытку написать это в глубоком ответе. Я не уверен, следует ли удалять следующие объекты, это означало бы, что объект, на который он указывает, также удаляется. Следующий похож на указатель на следующий CustomTypes . Это всего лишь ссылка для перехода к следующему CustomType . Связывание выглядит примерно так: en.wikipedia.org/wiki/Skip_list#/media/File:Skip_list.svg .

2. @Sandro4912 Добро пожаловать! Объекты, на которые указывают указатели в next «массиве», не будут удалены ни в одной из описанных выше реализаций. Вторая реализация делает то же, что и первая, но она позволяет избежать доступа к массиву за пределами (из-за чего программа имеет UB), а также запускает и завершает время жизни объектов-указателей (возможно, излишне), но не к фактическим объектам, на которые указывают указатели.