2014-02-05 3 views
4

Предположим, что я хочу построить векторный контейнер, который unlike 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(), но это действительно не то же самое.

+2

Вы хотите создать контейнер, который будет действовать как 'std :: vector' после того, как вы выберете' reserve'? Из [this ref] (http://www.cplusplus.com/reference/vector/vector/reserve/): «Требует, чтобы векторная емкость была по крайней мере достаточной, чтобы содержать n элементов». , –

+0

Похоже, что это не тот вектор, который вы хотите вообще. Что случилось с ассоциативным контейнером? –

+0

@ wesley.mesquita Только частично. Да, я хочу, чтобы распределение было «зарезервировано», но я также хочу, чтобы данные были полностью доступны. Например. 'size()' должен включать эти выделенные элементы. – iavr

ответ

2

Чтобы ответить на следующие вопросы:

  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. Память d eallocation
    3. Если !std::is_trivially_destructible<T> изменения размера флаги хранения соответственно

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

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

+0

Большое спасибо за ваш ответ! Я думаю, вы слишком далеко зашли. Чтобы все было просто, я упомянул в своем вопросе, что «вектор не содержит никакой дополнительной информации», поэтому флаги и решения не являются «плоскими», а не «на элемент». Это означает, что для некоторых типов 'T',' no_init' должно быть отключено, поэтому поведение должно быть таким же, как 'std :: vector'. Вопросы были примерно такими, какие именно? и «что я должен делать при уничтожении в отсутствие флагов? уничтожить элементы или нет?». Не могли бы вы подробно рассказать об этих ограничениях? – iavr

+0

Еще одна вещь. Тип может быть 'is_trivially_destructible', но он может иметь свой собственный конструктор, и, что еще хуже, конструктор может выделять некоторый ресурс. Это может быть против [правила из трех] (http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three), но все же это может означать, что я должен быть более консервативным и использовать дополнительно 'is_trivially_constructible' или даже' is_trivial'. Это правильно? Я должен признать, что я всегда терялся, пытаясь понять точное определение всех этих признаков и последствий моей проблемы. – iavr

+0

Если пользователь определил пользовательский деструктор, который освобождает некоторую память, тогда класс не является тривиально разрушаемым: http://www.cplusplus.com/reference/type_traits/is_trivially_destructible/ Если такого деструктора нет, но конструктор по-прежнему выделяет некоторую память , то это утечка памяти и ошибка на стороне пользователя, а не логика контейнера. – Antoine

Смежные вопросы