Пользовательский аллокатор для std::vector‹char› игнорируется

Я пытался использовать собственный распределитель для std::vector<char>, но заметил, что std::vector не нуждается/не использует какие-либо функции-члены из моего распределителя. Как это возможно?

#include <vector>

struct A : private std::allocator<char> {
   typedef std::allocator<char> alloc;
   using alloc::value_type;
   using alloc::pointer;
   using alloc::const_pointer;
   using alloc::difference_type;
   using alloc::size_type;
   using alloc::rebind;
   // member functions have been removed, since the program compiles without them
};

int main() {
    std::vector<char, A> v;
    v.resize(4000);
    for (auto& c : v)
      if (c)
         return 1; // never happens in my environment
   return 0; // all elements initialized to 0. How is this possible?
}

Я пробовал вышеуказанную программу с онлайн-компилятором С++ 11 (LiveWorkSpace), предоставляя g++ 4.7.2, 4.8 и 4.6.3.

В основном allocate(), deallocate(), construct() и destroy() не определены в моем распределителе, но программа компилируется, и все элементы будут инициализированы до 0.


person Martin    schedule 05.03.2013    source источник


Ответы (2)


Стандартная библиотека GCC всегда будет перепривязывать предоставленный распределитель, поэтому внутри она делает что-то вроде этого (в C++03):

typedef Alloc::template rebind<value_type>::other _Allocator_type;

(В С++ 11 используется allocator_traits, но в этом случае результат тот же.)

Затем вектор сохраняет объект этого типа внутри и использует его для всех (де)распределений.

Поскольку вы не определили шаблон члена rebind в своем распределителе, вы только что повторно объявили шаблон из базового класса, результатом перепривязки будет std::allocator<value_type>, а не ваш собственный тип. std::allocator, конечно, предоставляет все эти функции, так что используются именно они, независимо от того, определяете вы их для своего собственного типа или нет.

Вы можете исправить это, добавив это в свой распределитель вместо using alloc::rebind;, чтобы vector хранил и использовал A внутри:

struct A : private std::allocator<char> {
    template<typename U>
      struct rebind {
        typedef A other;
      };

Н.Б. это будет работать только для vector, потому что vector не обязательно повторно связывать распределитель (пользователи должны создавать экземпляр шаблона с помощью allocator<value_type>, но vector GCC все равно перепривязывает, так что, если пользователи создают экземпляр vector<int, std::allocator<char>>, он все еще работает.) Для контейнеров на основе узлов например, std::set, ваш распределитель должен быть шаблоном, который можно восстановить, потому что контейнер должен выделять свои внутренние типы узлов, а не value_type, поэтому ему нужно, чтобы Alloc::rebind<internal_node_type>::other был действительным.

person Jonathan Wakely    schedule 05.03.2013
comment
На самом деле, почему желательно, чтобы он все еще работал, если пользователь предоставляет std::allocator<char> в качестве распределителя для vector<int>? Разве не имеет смысла получить ошибку компилятора? - person Andy Prowl; 05.03.2013
comment
Я не уверен, что это желательно лично, но так было всегда. Для других контейнеров это имеет больше смысла: если пользователь говорит std::map<int, int, std::less<int>, std::allocator<std::pair<int, int>>>, это удобно принять, даже если технически распределитель должен быть std::allocator<std::pair<const int, int>> - person Jonathan Wakely; 05.03.2013
comment
@AndyProwl - распределители действительно должны быть параметрами шаблона шаблона, но параметров шаблона шаблона не существовало во время разработки STL (ПРИМЕЧАНИЕ: STL, а не Стандартная библиотека) , поэтому rebind был создан. - person Pete Becker; 05.03.2013
comment
@PeteBecker: Правильно ли я понимаю, что это не параметр шаблона шаблона в C++ 11 из-за обратной совместимости? - person Andy Prowl; 05.03.2013
comment
Немного не по теме: как будут распределяться внутренние поля контейнера (счетчик, указатель и т. д.)? Можно ли указать через размещение new ? (Я предполагаю, что для такого шаблона нет возможности указать оператор new/delete) Как насчет выделения вложенных контейнеров - будут ли они полностью (внутренние структуры + данные) помещаться в память, выделенную из самого верхнего распределителя? - person Red XIII; 05.03.2013
comment
Внутренние поля не выделяются, они просто члены объекта, поэтому существуют везде, где в памяти существует объект. Вложенные контейнеры ничем не отличаются от элементов любого другого типа: сам объект и его члены будут помещены в память, выделенную контейнером, если он выделит свою собственную память (как это сделает вложенный контейнер), то эта память будет в другом месте по адресу. космос. См. std::scoped_allocator_adaptor для утилиты, которая заставляет элементы наследовать распределитель из своего контейнера. - person Jonathan Wakely; 05.03.2013
comment
@JonathanWakely Имея std::vector<int>, как бы я поставил его на арену? auto v = new (arena_ptr) std::vector<int>{};? - person Red XIII; 05.03.2013
comment
пожалуйста, начните свой собственный вопрос вместо того, чтобы захватывать комментарии этого - person Jonathan Wakely; 05.03.2013
comment
@AndyProwl - учитывая, что rebind является частью распределителя, мало что выиграет от перехода на параметр шаблона шаблона. Так что да, обратная совместимость. - person Pete Becker; 05.03.2013
comment
Вау, я знал, что перепривязка полезна для контейнеров на основе узлов, которые на самом деле выделяют не value_type, а некоторые внутренние структуры данных. Но я не знал, что std::vector даже разрешено перепривязывать и не использовать предоставленный распределитель, так как я не думаю, что кто-то требует, чтобы я перепривязывал свой собственный распределитель к самому себе. Таким образом, со свободой перепривязки std::vector или нет я, в конце концов, никогда не смогу быть уверенным, с каким распределителем действительно работает std::vector, если я не гарантирую повторную привязку к тому же распределителю. - person Christian Rau; 05.03.2013
comment
@ChristianRau, перепривязка не просто полезна, она необходима. Если повторная привязка не работает (либо через A::rebind<U>::other, либо через allocator_traits<A>::rebind_alloc<U>), ваш тип не соответствует требованиям распределителя, поэтому ваша программа недействительна. Если ваш тип соответствует требованиям и перепривязка работает, то поведение vector GCC в любом случае невидимо и безвредно. - person Jonathan Wakely; 05.03.2013
comment
@JonathanWakely Итак, вы имеете в виду, что распределитель требуется по стандарту для rebind того же типа распределителя (конечно, с другим аргументом шаблона), и полностью запрещено иметь A::rebind<U>::other typedef для некоторого типа распределителя B совершенно не связан с A? - person Christian Rau; 05.03.2013
comment
@ChristianRau, да. Что требуется, так это то, что A1::rebind<A2::value_type>::other::rebind<A1::value_type>::other совпадает с A1 для всех A2, т. е. вы можете выполнить круговое путешествие, включая случай, когда A1 и A2 одного типа. Таким образом, A1::rebind<A1::value_type>::other должен быть того же типа, что и A1. См. Таблицу 28 в [allocator.requirements]. - person Jonathan Wakely; 05.03.2013

vector перепривязывает распределитель. Когда вы перенесете его в область действия из std::allocator, A::rebind<T>::other будет просто std::allocator<T>. Так что все работает нормально.

person R. Martinho Fernandes    schedule 05.03.2013