размещение/удаление массива элементов не тривиального типа на вручную выделенной памяти. Тк в проекте поддерживается cpp17 по итогу остановился на std::uninitialized_default_construct_n + std::destroy_n. Создание элементов можно заменить на placement new для массива - конструкторы отрабатывают корректно, но вот что делает и как работает placement delete я не разобрался. Кто-то работал с 'non-allocating placement deallocation functions' или это нечто ненужное и до C++17 необходимо использовать цикл + ручной вызов деструктора по указателю ~T()?
https://en.cppreference.com/w/cpp/memory/new/operator_delete
Вот тут можно посмотреть и запустить код
https://coliru.stacked-crooked.com/view?id=0885bd40bb355840
https://godbolt.org/z/1z6537Kve
Судя по примечаниям (13)(14) на cppref это не нужно руками звать никогда.
всякие std::destroy_n это сахар в 17++, вот и все. они делают тоже самое, просто сами, ты как бы экономишь время вот и все. если деструктор нужен, да ты его должен вызвать. всякие алигн стораджы и юнионы, используют по большей части во всяких эмбедетах, когда память всегда четко распределена, но возможно не известно под какие драйвера(девайсы) тебе как бы из внешки прилетит инфа. мол 10 датчиков дыма 15 пожарных. ты делаешь placement new.
0) я не писал продакшн код ни с одной из этих фич С++ и его стандартной библиотеки, но провел прилично времени за главами стандарта про объектную модель 1) вызов недеаллоцирующего operator delete[]() (не путать с delete expression) ничего не делает и никакую семантику в себе не несет. то есть вы можете его вызвать, и вам ничего за это не будет, но он не закончит время жизни объектов, созданных new, как вы можете от него ожидать 2) смысл существования этой перегрузки operator delete[]() изложен в стандарте. от себя могу добавить предположение, что и симметрии ради с неаллоцирующим operator new[]() 3) механизм работы placement delete постигнуть невозможно, так как такого выражения не существует — delete expression не предоставляет возможность передавать дополнительные аргументы в функцию деаллокации, в отличие от new 4) создавая объекты A через placement array new, вы не переиспользуете отдельные aligned_storage под хранение объектов A, а переиспользуете сам массив aligned_storag'ей 5.1) разница в том, что в этом случае вы не можете использовать арифметику указателей на основе place для того, чтобы получать указатели на объекты A, потому что массива place больше нет. для этого годится только указатель, который вернет new[] (и который вы игнорируете), либо отмытый (std::launder) ptr или любой другой указатель на начало place. 5.2) а еще это выстрелит, если вы усилите требования к выравниванию aligned_storage (например, чтобы выравнивать по 64-байтовым кэш-линиям), потому что нет возможности передать выравнивание в placement new[] 6) ваше С++17 решение совершенно верное, и если вам нужно портировать его туда, где эти алгоритмы недоступны, то проще и надежнее всего написать руками то же, что они делают под капотом, а именно вызывают placement new для каждого aligned_storage и так же по одному вызывают деструкторы A потом 7) возможно, вы уже знаете или поняли к этому моменту, что даже после успешного вызова std::uninitialized_default_construct_n вы не можете считать place массивом объектов А и заниматься арифметикой указателей на основе этого предположения — он остается массивом aligned_storage 8) у меня нет внятного ответа, почему в этом случае delete[] творит такую дичь. в стандарте я вижу лишь, что он должен работать. видимо, как обычно плохо смотрю возможно, @ddvamp может что-то добавить
Влад, спасибо за развёрнутый комментарий. По пункту 7 я понимаю, что если выравнивание будет отличаться, то все взлетит на воздух и нужно отдельно использовать каждую ячейку стороджа.
правда, это UB даже при совпадающем выравнивании. что может пойти не так в этом случае в голову не приходит, но разворачивать такое на проде я бы не рискнул
typename std::aligned_storage<1024 * sizeof(T), alignof(T)>::type data; так было-бы лучше? Реальный код использует результат std::allocator или custom где память уже выровнена по странице да и объекты сами по размеру кеш линии.
чтобы потом сделать new (data) T[1024]? может, и прокатило бы, если бы вы писали под 20 стандарт и были довольны стандартным operator new[](std::size_t, void*). если эти условия не соблюдаются, то вы упираетесь в стену имя которой array allocation overhead лучше всего взять С++17 решение, и если нужно обращаться по индексу, то написать простейшую обертку над reinterpret_cast'ом (godbolt). всевозможные проверки оставил за скобками
По поводу п.8: в примере для delete expression не работает отбрасывание вызова deallocation function http://eel.is/c++draft/expr.delete#7.3, т.к. allocation function является omitted только для constant expression http://eel.is/c++draft/expr.new#13 Как следствие получаем UB из-за нарушения предусловия deallocation function http://eel.is/c++draft/new.delete.array#9
я согласен, что оно так получается в итоге, но мне не понятно, зачем она вызывается (и зачем ее считать вызванной) для new, которому не нужно выделять память http://eel.is/c++draft/expr.new#10
Как вариант потому, что пользователь может её заменить на аналог с дополнительным функционалом (зануление памяти то же). А в прикладном смысле не понятно, откуда брать информацию о том, размещающий был вызов new или аллоцирующий.
вариант с заменой звучит интересно, но представляется проблематичным в реализации ввиду этого пункта
Это относится только к глобальным функциям, на уровне класса их заменять можно.
Обсуждают сегодня