построение вектора для хранения неинициализированного хранилища

Допустим, я хочу построить векторный контейнер, в отличие от 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 при условии, что пользователь не пытается использовать неинициализированный элемент перед его созданием. Это может звучать как сильное предположение, но доступ к элементам только в пределах диапазона вектора не настолько отличается от предположения, и это так часто встречается.

Итак, мои вопросы:

  1. Для каких типов было бы безопасно разрешить этот тип неинициализированной операции, как в vec <T> a(no_init)? Похоже is_pod было бы хорошо и, скорее всего, is_trivial также. Я не хотел бы ставить больше ограничений, чем необходимо.

  2. Разрушение должно выполняться всегда или только для некоторых типов? Будет ли то же ограничение, что и выше? Как насчет is_trivially_destructible? Идея состоит в том, что уничтожение элемента, который имеет не был построен или наоборот (не разрушение построенного элемента) не должно причинить вреда.

  3. Есть ли серьезный недостаток в этой попытке, кроме очевидного риска возложения большей ответственности на пользователя?

Все дело в том, что когда пользователю нужна такая функциональность для повышения производительности, решения более низкого уровня, такие как std::get_temporary_buffer или ручное распределение (например, с operator new()) может быть более рискованным с точки зрения утечки. Я знаю о std::vector::emplace_back() но это действительно не то же самое.

4

Решение

Чтобы ответить на вопросы:

  1. нет ограничений на T : если он работает для стандартных контейнеров, он работает для вас.
  2. уничтожение является условным, вы можете статически отключить его, если std::is_trivially_destructible<T>иначе вы должны отслеживать созданные элементы и удалять только те, которые были фактически созданы.
  3. Я не вижу серьезного недостатка в вашей идее, но убедитесь, что оно того стоит: профилируйте ваш вариант использования и убедитесь, что вы действительно тратите много времени на инициализацию элементов.

Я делаю предположение, что вы реализуете свой контейнер как блок непрерывной памяти размером size() * sizeof(T), Кроме того, если деструктор элемента должен быть вызван, т.е. !std::is_trivially_destructible<T>необходимо включить дополнительное хранилище, например std::vector<bool> из size() элементы используют для обозначения элементов для уничтожения.

В основном, если T тривиально разрушаемо, вы просто инициализируете, когда пользователь спрашивает, и не пытаетесь уничтожить что-либо. Иначе, все немного сложнее, и вам нужно отследить, какой элемент был создан, а какой не инициализирован, так что вы уничтожаете только то, что нужно.

  • увеличение размера или создание контейнера:
    1. если !std::is_trivially_destructible<T> изменить размер хранилища флагов соответственно
    2. Выделение памяти
    3. Дополнительная инициализация в зависимости от того, что спросил пользователь:
      • no_init => если !std::is_trivially_destructible<T>, отметьте элементы как неинициализированные. Остальное ничего не делать.
      • (Args...) => если std::is_constructible<T, class... Args> вызвать этот конструктор для каждого элемента. Если !std::is_trivially_destructible<T>, отметьте элементы как построенные.
  • сокращение размеров или разрушение контейнера:
    1. Опциональное уничтожение:
      • Если std::is_trivially_destructible<T> ничего не делать
      • иначе для каждого элемента, если он помечен как созданный, вызовите его деструктор
    2. Освобождение памяти
    3. Если !std::is_trivially_destructible<T> изменить размер хранилища флагов соответственно

С точки зрения производительности, если T тривиально разрушаемы, все прекрасно. Если у него есть деструктор, все становится более противоречивым: вы получаете вызовы некоторых конструкторов / деструкторов, но вам необходимо поддерживать дополнительное хранилище флагов — в конце концов, это зависит от того, достаточно ли сложны ваши конструкторы / деструкторы.

Также, как некоторые предложили в комментариях, вы можете просто использовать ассоциативный массив на основе std::unordered_map, добавить size_t vector_size поле, внедрить resize и переопределить size, Таким образом, неинициализированные элементы даже не будут сохранены. С другой стороны, индексирование будет медленнее.

2

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]