Допустим, я хочу построить векторный контейнер, в отличие от std :: vector, позволяет неинициализированное хранилище. Использование контейнера, скажем, vec <T>
было бы примерно так:
Пользователь явно заявляет, что вектору следует выделить N неинициализированных элементов:
vec <T> a(N, no_init);
В какой-то момент, когда данные известны, пользователь явно инициализирует элемент в позиции n
используя аргументы args...
:
a.init(n, args...);
ИЛИ, что эквивалентно, создает элемент вручную:
new (&a[n]) T(args...);
Другие операции могут инициализироваться или копироваться более массово (например, std::uninitialized_copy
), но это только для удобства; основная базовая операция такая же.
После выполнения некоторой задачи вектор можно оставить с некоторыми инициализированными элементами, а другие — нет. Вектор не содержит никакой дополнительной информации, поэтому в конечном итоге, перед освобождением памяти, он либо уничтожает все элементы в любом случае, либо разрушает только в зависимости от T
,
Я почти уверен, что это можно сделать, только я не уверен в последствиях. Естественно, мы хотели бы, чтобы эта структура была безопасной для всех типов T
при условии, что пользователь не пытается использовать неинициализированный элемент перед его созданием. Это может звучать как сильное предположение, но доступ к элементам только в пределах диапазона вектора не настолько отличается от предположения, и это так часто встречается.
Итак, мои вопросы:
Для каких типов было бы безопасно разрешить этот тип неинициализированной операции, как в vec <T> a(no_init)
? Похоже is_pod
было бы хорошо и, скорее всего, is_trivial
также. Я не хотел бы ставить больше ограничений, чем необходимо.
Разрушение должно выполняться всегда или только для некоторых типов? Будет ли то же ограничение, что и выше? Как насчет is_trivially_destructible
? Идея состоит в том, что уничтожение элемента, который имеет не был построен или наоборот (не разрушение построенного элемента) не должно причинить вреда.
Есть ли серьезный недостаток в этой попытке, кроме очевидного риска возложения большей ответственности на пользователя?
Все дело в том, что когда пользователю нужна такая функциональность для повышения производительности, решения более низкого уровня, такие как std::get_temporary_buffer
или ручное распределение (например, с operator new()
) может быть более рискованным с точки зрения утечки. Я знаю о std::vector::emplace_back()
но это действительно не то же самое.
Чтобы ответить на вопросы:
T
: если он работает для стандартных контейнеров, он работает для вас.std::is_trivially_destructible<T>
иначе вы должны отслеживать созданные элементы и удалять только те, которые были фактически созданы.Я делаю предположение, что вы реализуете свой контейнер как блок непрерывной памяти размером size() * sizeof(T)
, Кроме того, если деструктор элемента должен быть вызван, т.е. !std::is_trivially_destructible<T>
необходимо включить дополнительное хранилище, например std::vector<bool>
из size()
элементы используют для обозначения элементов для уничтожения.
В основном, если T
тривиально разрушаемо, вы просто инициализируете, когда пользователь спрашивает, и не пытаетесь уничтожить что-либо. Иначе, все немного сложнее, и вам нужно отследить, какой элемент был создан, а какой не инициализирован, так что вы уничтожаете только то, что нужно.
!std::is_trivially_destructible<T>
изменить размер хранилища флагов соответственноno_init
=> если !std::is_trivially_destructible<T>
, отметьте элементы как неинициализированные. Остальное ничего не делать.(Args...)
=> если std::is_constructible<T, class... Args>
вызвать этот конструктор для каждого элемента. Если !std::is_trivially_destructible<T>
, отметьте элементы как построенные.std::is_trivially_destructible<T>
ничего не делать!std::is_trivially_destructible<T>
изменить размер хранилища флагов соответственноС точки зрения производительности, если T
тривиально разрушаемы, все прекрасно. Если у него есть деструктор, все становится более противоречивым: вы получаете вызовы некоторых конструкторов / деструкторов, но вам необходимо поддерживать дополнительное хранилище флагов — в конце концов, это зависит от того, достаточно ли сложны ваши конструкторы / деструкторы.
Также, как некоторые предложили в комментариях, вы можете просто использовать ассоциативный массив на основе std::unordered_map
, добавить size_t vector_size
поле, внедрить resize
и переопределить size
, Таким образом, неинициализированные элементы даже не будут сохранены. С другой стороны, индексирование будет медленнее.
Других решений пока нет …