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::array<A, 0> a;
хотя было бы хорошо
Ответный вопрос: Кто или что учит вас писать такой код в стиле «C»?
@TedLyngmo Хотя это не подходящая замена гибкого члена массива.
@HolyBlackCat Нет, конечно. Это будет всего лишь заменой массива нулевого размера и действительно полезно только в общем программировании.
FAM — это строго старая вещь C, в C++ это расширение. Некоторые компиляторы рассматривают это как размер 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
-подобный контейнер, бессмысленно реализовывать его самостоятельно вместо того, чтобы использовать то, что уже реализовано за вас.
nvm уже удалил мой комментарий;) Я слишком быстро сработал при создании/удалении;)
std::vector
и друзья против члена гибкого массива — это не вещь C++ и C. Даже в C++ может возникнуть законная необходимость в гибком элементе массива или его эквиваленте (избегая одного дополнительного выделения кучи).
@HolyBlackCat Вы всегда можете заменить элемент гибкого массива, создав отдельные объекты в выделенном хранилище. Это верно и для C. Гибкий элемент массива предназначен только для удобства и упрощает доступ к объектам после заголовка. В C++ технически это более ограничительно, поскольку указатель на заголовок не может использоваться для доступа к объектам после него, но передачи указателя на базовое хранилище по-прежнему достаточно, и в C++ его можно сделать типобезопасным, обернув указатель. в новый тип, как я предложил здесь.
@HolyBlackCat Он не обязательно должен соответствовать обычному макету std::vector
и может быть просто типизированным указателем на базовое хранилище с методами доступа, которые выполняют арифметику указателей + reinterpret_cast
/launder
в хранилище, чтобы получить то же удобство, что и гибкие члены массива в C.
@HolyBlackCat Это становится громоздким только в том случае, если предполагается, что объект с гибким массивом, следующим за ним, будет совместно использоваться в одном распределении с другими объектами. Тогда пришлось бы писать обертку-указатель для каждой возможной комбинации объектов в хранилище. Это единственный случай, когда я вижу, что гибкий элемент массива является лучшим решением с точки зрения практичности, хотя предположительно необходимая информация для доступа к отдельным объектам все еще должна присутствовать в программе и может быть включена в тип оболочки, чтобы она все еще работала. .
Почему вы добавляете массив нулевого размера? Для массивов «изменяемого размера» используйте std::vector (и забудьте о неразмерных массивах в стиле «C»). Подсказка: в текущем C++ использование голых операторов new/delete не рекомендуется. (Используйте контейнеры стандартной библиотеки или хотя бы std::make_unique/std::make_shared).