Персонализиран разпределител за 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?
}

Опитвах горната програма с онлайн C++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;

(В C++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;
      };

N.B. това ще работи само за 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? (Предполагам, че няма възможност за указване на нов/изтриващ оператор за такъв шаблон) Какво ще кажете за разпределението на вложени контейнери - ще бъдат ли те напълно (вътрешни структури + данни) поставени в паметта, разпределена от най-горния разпределител? - 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_types, а някои вътрешни структури от данни. Но не знаех, че на 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 typedefed към някакъв тип разпределител 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