#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*) const’
13 | 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>
, а не astd::unique_ptr<CustomType, CustomTypeDeleter>
?2. Кроме того, почему вы создаете a
std::unique_ptr<std::byte[]>
вместо выделения памяти сnew
помощью ormalloc
?
Ответ №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), а также запускает и завершает время жизни объектов-указателей (возможно, излишне), но не к фактическим объектам, на которые указывают указатели.