Подтвердить что ты не робот

Может ли sizeof (std:: list <T>) различаться для разных типов T?

Можно ли предположить, что для любого типа T тип std::list<T> будет иметь тот же постоянный размер? Просто, чтобы понять это, я имею в виду только размер "основного" типа, а не выделенную им память.

Мне кажется логичным предположить, что размер T сам по себе должен влиять только на размер узлов списка, распределенных с помощью распределителя.

Однако есть две вещи, которые могут вызвать вариацию sizeof(std::list<T>), о которой я могу думать:

  • Стандартная библиотека С++ пытается "оптимизировать" тип std::list, помещая некоторые из экземпляров T в сам std::list<T>. Мне это кажется плохой идеей, и это, вероятно, нарушает требования "постоянного времени", установленные стандартом;
  • Стандартный С++ с библиотечными специализациями std::list<T> для некоторых типов, со специализациями разных размеров.

Я не могу придумать никаких применений для (1) или (2), но я могу ошибаться.

То, что я собираюсь достичь, - это использовать распределяющий срез для фиксированного размера для класса шаблона, используя std::list<T> внутри.


В качестве примечания: речь идет о дисперсии для разных типов T, а не для разных распределителей. Я буду иметь явный контроль над всеми экземплярами, и все они будут использовать std::allocator.

4b9b3361

Ответ 1

Да, он может.

Стандарт не уточняет никаких гарантий относительно размера объекта list<T>, насколько мне известно. Тем не менее, есть и другие гарантии.


Разделим некоторые вещи:

Стандартная библиотека С++ пытается "оптимизировать" тип std::list, помещая некоторые из экземпляров T в сам std::list<T>. Мне это кажется плохой идеей, и это, вероятно, нарушает требования "постоянного времени", установленные стандартом;

Он не нарушает требование "постоянного времени" каким-либо образом, пока это число ограничено, так как O (5) - O (1). Однако во время операций splice узлы должны перемещаться из одного списка в другой без перемещения в памяти. Таким образом, местное хранилище запрещено.

Стандартный С++, имеющий библиотечные специализации std::list<T> для некоторых типов, со специализациями разных размеров.

Поскольку мы уже исключили возможность локального хранения, трудно представить себе какие-либо другие варианты базового типа std::list<T>.


И будем беспокоиться о том, что важно:

Память, выделяемая std::list<T>, обеспечивается его вторым аргументом шаблона: распределителем. Большинство распределителей (включая std::allocator<T>, которые по умолчанию) используют простой тип указателя: T*... однако распределитель не должен изменять этот тип.

Поэтому, изменив параметр allocator (и, точнее, его тип указателя), естественно изменит размер контейнера std::list во всех реализациях, которые я видел.

Обратите внимание, что сам распределитель может быть специализированным для некоторых типов, однако с магией повторного воспроизведения его немного сложнее достичь.

Ответ 2

В главе 23 стандарта С++ 11 (Контейнеры) ничего не говорится об их размерах, поэтому технически вы не можете предположить, что размер будет постоянным. Разработчики могли (теоретически) специализировать некоторый контейнер для определенного примитива таким образом, который влияет на его след.

На практике вы можете использовать статическое утверждение sizeof(list<T>) == sizeof(list<int>) и использовать sizeof(list<int>) как "фиксированный" размер. Хуже того, что может произойти ошибка компиляции.

Ответ 3

Распределитель среза с фиксированным размером для класса с использованием std::list<T> имеет мало смысла, если вы также не используете распределяющий ломтик фиксированного размера для самого списка, вероятно, будет намного больше распределений для элементов списка, чем заголовков списков.

Теперь вы, безусловно, можете предоставить свой собственный распределитель среза фиксированного размера в списки, используя свой второй аргумент шаблона - распределитель. Однако есть поворот: вы не знаете, насколько большой должен быть срез. A list<T> принимает allocator<T>, но ему действительно нужно allocator<U>, где U - структура, содержащая указатели prev/next и T. Чтобы получить это, он вызывает метод rebind, имеющий подпись template <typename T> template <typename T> allocator<U> allocator<T>::rebind<U>().

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