Обратите внимание: эта проблема возникает
Справедливости ради стоит отметить, что сразу же за положением об эквивалентности однотипных распределителей памяти в Стандарт включен следующий текст: «…Авторам реализаций рекомендуется создавать библиотеки, которые… поддерживают неэквивалентные распределители. В таких реализациях… семантика контейнеров и алгоритмов для неэквивалентных экземпляров распределителей определяется самой реализацией».
Трогательное проявление заботы, однако пользователю STL, рассматривающему возможность создания нестандартного распределителя с состоянием, это не дает практически ничего. Этим положением можно воспользоваться только в том случае, если вы уверены в том, что используемая реализация STL поддерживает неэквивалентные распределители, готовы потратить время на углубленное изучение документации, чтобы узнать, подходит ли вам «определяемое самой реализацией» поведение неэквивалентных распределителей, и вас не беспокоят проблемы с переносом кода в реализации STL, в которых эта возможность может отсутствовать. Короче говоря, это положение (для особо любознательных — абзац 5 раздела 20.1.5) лишь выражает некие благие намерения по поводу будущего распределителей. До тех пор пока эти благие намерения не воплотятся в жизнь, программисты, желающие обеспечить переносимость своих программ, должны ограничиваться распределителями без состояния.
Выше уже говорилось о том, что распределители обладают определенным сходством с оператором new
— они тоже занимаются выделением физической памяти, но имеют другой интерфейс. Чтобы убедиться в этом, достаточно рассмотреть объявления стандартных форм operator new
и allocator
:
void* operator new(size_t bytes);
pointer allocator
// Напоминаю: pointer - определение типа.
//практически всегда эквивалентное T*
В обоих случаях передается параметр, определяющий объем выделяемой памяти, но в случае с оператором new
указывается конкретный объем в байтах, а в случае с allocator
указывается количество объектов T
, размещаемых в памяти. Например, на платформе, где sizeof (int)==4
, при выделении памяти для одного числа int
оператору new
передается число 4, а allocator
— число 1. Для оператора new
параметр относится к типу size_t
, а для функции allocate
— к типу allocator
, В обоих случаях это целочисленная величина без знака, причем allocator
обычно является простым определением типа для size_t
. В этом несоответствии нет ничего страшного, однако разные правила передачи параметров оператору new
и allocator
усложняют использование готовых пользовательских версий new в разработке нестандартных распределителей.
Оператор new отличается от allocator
и типом возвращаемого значения. Оператор new
возвращает void*
, традиционный способ представления указателя на неинициализированную память в C++. Функция allocator
возвращает T*
(через определение типа pointer
), что не только нетрадиционно, но и отдает мошенничеством. Указатель, возвращаемый allocator
, не может указывать на объект T
, поскольку этот объект еще не был сконструирован! STL косвенно предполагает, что сторона, вызывающая allocator
, сконструирует в полученной памяти один или несколько объектов T
(вероятно, посредством allocator
, uninitialized_fill
или raw_storage_iterator
), хотя в случае vector::reseve
или string::reseve
этого может никогда не произойти (совет 13). Различия в типах возвращаемых значений оператора new
и allocator
означают изменение концептуальной модели неинициализированной памяти, что также затрудняет применение опыта реализации оператора new
к разработке нестандартных распределителей.
Мы подошли к последней странности распределителей памяти в STL: большинство стандартных контейнеров никогда не вызывает распределителей, с которыми они ассоциируются. Два примера:
list
// Контейнер никогда не вызывает
// allocator
set