Как устранить ошибку «неизвестный размер массива при удалении» при использовании класса с гибким элементом массива (FAM) и нетривиальным деструктором?

struct A {
  ~A () {}
};

struct S {
  S() : i(0) { }
  ~S() {}
  int i;
  
  // This fails:
  //
  // A a[];
  A a[0];
};
int main()
{
    struct A aaa;
    return 0;
}

Обратите внимание: если массив нулевого размера заменить элементом гибкого массива (FAM) A a[];, возникнет ошибка: error: unknown array size in delete. Этот пример я нашел здесь.

Я столкнулся с этой проблемой в своем собственном динамически расширяемом классе контейнера.

Мои вопросы: как решить? Является ли массив нулевого размера допустимой заменой и хорошо ли работает? Например, чтобы использовать operator new, чтобы выделить память для экземпляра класса, и использовать operator delete, чтобы освободить ее, следующим образом:

size_t totalSize = sizeof(MyContainer) + capacity * sizeof(MyElement);
void* memory = operator new(totalSize);
MyContainer* container = new (memory) MyContainer();
// ...
operator delete((void*)container);

Почему вы добавляете массив нулевого размера? Для массивов «изменяемого размера» используйте std::vector (и забудьте о неразмерных массивах в стиле «C»). Подсказка: в текущем C++ использование голых операторов new/delete не рекомендуется. (Используйте контейнеры стандартной библиотеки или хотя бы std::make_unique/std::make_shared).

Pepijn Kramer 31.08.2024 11:34

Массивы нулевого размера — это расширение, доступное не во всех реализациях. Не используйте это. std::array<A, 0> a; хотя было бы хорошо

Ted Lyngmo 31.08.2024 11:35

Ответный вопрос: Кто или что учит вас писать такой код в стиле «C»?

Pepijn Kramer 31.08.2024 11:36

@TedLyngmo Хотя это не подходящая замена гибкого члена массива.

HolyBlackCat 31.08.2024 11:49

@HolyBlackCat Нет, конечно. Это будет всего лишь заменой массива нулевого размера и действительно полезно только в общем программировании.

Ted Lyngmo 31.08.2024 12:06

FAM — это строго старая вещь C, в C++ это расширение. Некоторые компиляторы рассматривают это как размер 1, и размер структуры будет неправильным.

Swift - Friday Pie 31.08.2024 12:45
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
68
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Гибкие члены массива не существуют в стандарте C++. Они могут (а могут и не поддерживаться) вашим конкретным компилятором как расширение языка. Если это так, то их использование с другими возможностями C++, вероятно, будет сильно ограничено.

Замена гибкого члена массива на A a[0]; не улучшает его. В стандарте C++ также нельзя объявить, что массив имеет нулевой размер. Опять же, если он скомпилируется, это будет какое-то языковое расширение вашего конкретного компилятора. И, в отличие от правильных гибких членов массива, они даже не являются стандартным C, что делает еще менее ясным, какова семантика.

delete не может работать с гибким элементом массива типа с нетривиальным деструктором. delete необходимо знать размер массива, поскольку ему потребуется вызвать нетривиальный деструктор каждого элемента массива. Но у него нет возможности узнать этот размер.

Вам необходимо самостоятельно выделить память с помощью operator new/operator new[], создать объекты в памяти, полученной таким образом, с помощью Placement-new или std::construct_at и вручную уничтожить каждый созданный вами элемент с помощью явного вызова деструктора или вызова std::destroy_at. Затем вам нужно вручную освободить память с помощью operator delete/operator delete[].


Вы пытаетесь применить шаблоны C к C++, которые не дают никаких преимуществ в C++.

Гибкий член массива не требуется для реализации того, что вы хотите. Пользователь не должен использовать MyContainer* для ссылки на экземпляр вашего контейнера. Вместо этого MyContainer должен использоваться пользователем по значению, а MyContainer должен быть типом класса, содержащим указатель на динамически выделенную память. Будет ли член i храниться в MyContainer или в начале выделения указателя, который он содержит, зависит от вас.

Посмотрите, как реализуются реализации стандартной библиотеки std::vector. По сути, вы пытаетесь реализовать то же самое, но с ограниченным набором функций. std::vector является универсальным по типам, имеет строгие гарантии безопасности исключений, имеет лучшую алгоритмическую сложность, чем то, что вы пытаетесь сделать, работает с универсальными распределителями вместо new/delete и т. д.

Кроме того, эти реализации сохраняют размер не в начале выделения, а в самом объекте-контейнере, но это незначительное отличие. Корректная реализация такого контейнера на C++ нетривиальна и требует хорошего понимания объектной модели.

@PepijnKramer Они пытаются реализовать свой собственный контейнер, похожий на std::vector. Я предполагаю, что у них есть для этого какая-то конкретная причина. Конечно, если им просто нужен какой-то std::vector-подобный контейнер, бессмысленно реализовывать его самостоятельно вместо того, чтобы использовать то, что уже реализовано за вас.

user17732522 31.08.2024 12:22

nvm уже удалил мой комментарий;) Я слишком быстро сработал при создании/удалении;)

Pepijn Kramer 31.08.2024 12:23
std::vector и друзья против члена гибкого массива — это не вещь C++ и C. Даже в C++ может возникнуть законная необходимость в гибком элементе массива или его эквиваленте (избегая одного дополнительного выделения кучи).
HolyBlackCat 31.08.2024 14:19

@HolyBlackCat Вы всегда можете заменить элемент гибкого массива, создав отдельные объекты в выделенном хранилище. Это верно и для C. Гибкий элемент массива предназначен только для удобства и упрощает доступ к объектам после заголовка. В C++ технически это более ограничительно, поскольку указатель на заголовок не может использоваться для доступа к объектам после него, но передачи указателя на базовое хранилище по-прежнему достаточно, и в C++ его можно сделать типобезопасным, обернув указатель. в новый тип, как я предложил здесь.

user17732522 31.08.2024 15:11

@HolyBlackCat Он не обязательно должен соответствовать обычному макету std::vector и может быть просто типизированным указателем на базовое хранилище с методами доступа, которые выполняют арифметику указателей + reinterpret_cast/launder в хранилище, чтобы получить то же удобство, что и гибкие члены массива в C.

user17732522 31.08.2024 15:12

@HolyBlackCat Это становится громоздким только в том случае, если предполагается, что объект с гибким массивом, следующим за ним, будет совместно использоваться в одном распределении с другими объектами. Тогда пришлось бы писать обертку-указатель для каждой возможной комбинации объектов в хранилище. Это единственный случай, когда я вижу, что гибкий элемент массива является лучшим решением с точки зрения практичности, хотя предположительно необходимая информация для доступа к отдельным объектам все еще должна присутствовать в программе и может быть включена в тип оболочки, чтобы она все еще работала. .

user17732522 31.08.2024 15:15

Другие вопросы по теме

Zephyr – C – глубокая копия структуры дает неожиданный результат
Fsnotify watcher использует слишком много памяти
Верно ли это понимание: если объект хранит в себе все, то копирование и перемещение — это на самом деле одно и то же?
Почему int **a = &(&val); привести к ошибке в C++?
Где на самом деле хранятся примитивы внутри объектов
Как мне настроить освобождаемый кеш в Golang (т. е. я могу освободить память при обнаружении нехватки памяти)?
Проблема с использованием unsynchronized_pool_resource для выделения pmr::vector
Почему std::coroutine_handle ссылается только на сопрограмму (через необработанный указатель), а не владеет ею (через std::unique_ptr)?
Когда пользовательский процесс заменяет страницу, находится ли виртуальный адрес страницы в пространстве пользователя или пространстве ядра?
Почему компилятор Rust удаляет неиспользуемые переменные в порядке, обратном их объявлению?