Почему типы std не предоставляют конструктор / назначение преобразования из источников, отличающихся распределителем

Например, почему не template< typename Elem, typename Traits, typename Alloc > basic_string { ... } предоставлять:

template< typename OtherAlloc >
basic_string( const basic_string< Elem, Traits, OtherAlloc >& a_Other ) { ... }

Казалось бы, довольно просто реализовать такой конструктор преобразования, который учитывает оба распределителя. Современное состояние дел делает взаимодействие между типами, просто различающимися по распределителям, очень обременительным.

2 ответа

Стандартный хеш также не разрешает использование функции распределителя. std::string а также std::wstring но нет std::basic_string<char, std::char_traits<char>, custom_alloc>, Кроме того, вы не можете создать unordered_map или же unordered_set с помощью всего лишь распределителя - вы также должны указать номер сегмента, который по умолчанию равен константе, определяемой реализацией, к которой вы не можете получить доступ, поэтому вам просто нужно что-то придумать. Поддержка, как правило, не очень хорошая.

Мне кажется, что относительно просто никто не предлагал такую ​​функцию и не исследовал эту область использования.

Проблема гораздо сложнее, чем кажется. Поскольку тип распределителя является частью типа объекта, между типами, которые отличаются только распределителем, допускается незначительное взаимодействие. Первый пример, который приходит на ум, заключается в том, что функция, которая принимает std::string по константной ссылке нельзя взять строку, которая использует другой распределитель. Один такой частный случай - во время строительства объектов. На самом деле, это, вероятно, сложнее.

Рассмотрим для примера следующий код:

// Assume that you could construct from a different allocator
std::vector<int, allocator1> f() {
   std::vector<int, allocator2> r;
   // fill in
   return r;
}
int main() {
   std::vector<int, allocator3> x = f();
}

Считают, что allocator1 является std::allocator<int> (то есть распределитель по умолчанию), что allocator2 использует локальную арену в стеке, и это allocator3 может использовать общую память. В теории код довольно прост, вектор r создается и заполняется данными, в операторе возврата создается новый временный объект путем копирования из r, и наконец x построен путем копирования из этого временного. Проблема в том, что стандарт позволяет (и компиляторам нравится) избегать максимально возможного копирования. В приведенном выше конкретном примере (и без учета распределителей) компилятор исключает обе копии и создает буфер только один раз, что быстро и эффективно. Но с распределителями, которые могут быть другими, NRVO и другие типы разрешения копирования должны быть отключены. (Если эти оптимизации включены, x в основном будет использовать allocator2, с локальной ареной, которая уже была разрушена, вызывая неопределенное поведение).

Включив конструкцию копирования из контейнеров с одним распределителем в другой, вы можете оказаться в беспорядке или в более глубоком беспорядке, чем в текущем стандарте, где вы можете вызывать всевозможные интересные проблемы с распределителями состояния (например, вы используете Распределители для каждого потока и перемещение данных в общую очередь может привести к тому, что один поток будет содержать объект, созданный распределителем для каждого потока, в другом потоке, и поскольку целью использования распределителей для каждого потока является избегание конфликтов на блокировок, вы могли бы создать условие гонки на очевидно безопасном коде....


Это старое предложение для комитета C++. На пути к лучшей модели распределения, которая поднимает некоторые проблемы с моделью распределения C++03 и предлагает полиморфный тип распределителя (который имеет свои собственные проблемы). Это делает для интересного чтения, но остерегайтесь деталей, не все так хорошо, как кажется, и есть немало ловушек при использовании любой опции (или версии C++11, которая похожа на C++03 версия)

Другие вопросы по тегам