Должен ли я передать allocator в качестве параметра функции? (мое непонимание относительно распределителя)

после того, как я изучаю о allocator в течение нескольких дней, прочитав некоторые статьи
(cppreference и Мы) ,
Я смущен тем, как управлять структурой данных для выделения памяти определенным образом.

я совершенно уверен, что что-то не так понял,
поэтому я разделю остальную часть вопроса на многие частей чтобы сделать мою ошибку легче ссылаться.

вот что я (mis)поймите: -

фрагмент

предположим, что B::generateCs() - это функция, которая создает список C из списка CPrototype.
The B::generateCs() используется B() конструктора:-

class C          {/*some trivial code*/};
class CPrototype {/*some trivial code*/};
class B {
    public: 
    std::vector<C> generateCs() {  
        std::vector<CPrototype> prototypes = getPrototypes();
        std::vector<C> result;                     //#X
        for(std::size_t n=0; n < prototypes.size(); n++) {
            //construct real object  (CPrototype->C)
            result.push_back( makeItBorn(prototypes[n]) ); 
        }
        return result;
    }
    std::vector<C> bField;    //#Y
    B() {
        this->bField = generateCs();    //#Y  ; "generateCs()" is called only here
    }
    //.... other function, e.g. "makeItBorn()" and "getPrototypes()"
};

в приведенном выше коде std::vector<C> в настоящее время используется общий default std::allocator.

для простоты, с этого момента, скажем, есть только 2 распределителя (рядом с std::allocator) ,
что я код сам или изменить где-то :-

  • HeapAllocator
  • StackAllocator

Часть 1 (#X)

этот фрагмент можно улучшить с помощью конкретного распределителя типов.
Его можно улучшить в 2 положениях. (#X и #Y)

std::vector<C> в строке #X кажется, переменная стека,
поэтому я должен использовать stack allocator :-

std::vector<C,StackAllocator> result;   //#X

это имеет тенденцию давать производительность прибыль. (#X закончена.)

Часть 2 (#Y)

далее, более трудная часть в B() конструктор. (#Y)
Было бы неплохо, если переменная bField имеет соответствующий протокол распределения.

просто кодирование звонящего использовать распределитель явно невозможно, потому что вызывающий конструктор может делать только то, что: -

std::allocator<B> bAllo;   
B* b = bAllo.allocate(1);   

, который не имеет никакого влияния на протокол распределения bField.

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

Часть 3

я не могу знать, является ли экземпляр B будет построен как переменная кучи или переменная стека.
Это важно, потому что эта информация важна для выбора правильного распределителя/протокола.

если я знаю, какой из них (куча или стек), я могу изменить объявление bField to быть:-

std::vector<C,StackAllocator> bField;     //.... or ....
std::vector<C,HeapAllocator> bField;     

к сожалению, с ограниченной информацией (я не знаю, какая это будет куча/стек, это может быть и то, и другое),
этот путь (используя std::vector) ведет в тупик.

Часть 4

поэтому лучший способ-передать распределитель в конструктор: -

MyVector<C> bField; //create my own "MyVector" that act almost like "std::vector"
B(Allocator* allo) {
    this->bField.setAllocationProtocol(allo);  //<-- run-time flexibility 
    this->bField = generateCs();   
}

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

кроме того, это единственный практический способ получить преимущество когерентности данных ниже, когда есть много абонентов, каждый из которых использует свой собственный кусок памяти: -

class System1 {
    Allocator* heapForSystem1;
    void test(){
        B b=B(heapForSystem1);
    }
};
class System2 {
    Allocator* heapForSystem2;
    void test(){
        B b=B(heapForSystem2);
    }
};

вопрос

  • где я начал ошибаться, как?
  • как я могу улучшить фрагмент, чтобы использовать соответствующий распределитель (#X и #Y)?
  • когда я должен передать распределитель в качестве параметра?

трудно найти практический пример использования распределителя.

редактировать (ответ Уолтер)

... использование другого, чем std:allocator, рекомендуется редко.

Это было бы ценное знание, если бы оно было надежным.

1. Существуют ли какие-либо книги/ссылки/ссылки/доказательства, подтверждающие это?
Список не поддерживает требование. (Он на самом деле поддерживает противоположное немного.)
Это из личного опыта?

2. Ответ как-то противоречит многим источникам. Прошу защиты.
Есть много источников, которые рекомендуют не использовать std:allocator<>.

более конкретно, они просто "шумиха", которую редко стоит использовать в реальном мире?

еще один маленький вопрос :-
Может ли претензия быть расширена до "большинство качественных игр редко используют пользовательский распределитель"?

3. Если я нахожусь в такой редкой ситуации, я должен заплатить за это, верно?

есть только 2 хороших способа:-

  • передача распределителя в качестве аргумента шаблона или
  • в качестве параметра функции (включая конструктор)
  • (еще один плохой подход-создать некоторые глобальные флаг о том, какой протокол использовать)

это правильно?

2 ответов


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

template<template <typename T> Allocator>
class B
{
public:
  using allocator = Allocator<C>
  using fieldcontainer = std::vector<C,allocator>;
  B(allocator alloc=allocator{})
  : bFields(create_fields(alloc)) {}
private:
  const fieldcontainer bFields;
  static fieldcontainer create_fields(allocator);
};

обратите внимание, однако, что есть экспериментальный поддержка полиморфного распределителя, что позволяет изменять поведение распределителя независимо от типа. Это, безусловно, предпочтительнее, чем создавать свои собственные MyVector<> шаблон.

обратите внимание, что использование другого, чем std:allocator<> рекомендуется, только если есть веская причина. Возможны следующие случаи.

  1. распределитель стека может быть предпочтительным для небольших объектов, которые часто выделяются и де-выделяются, но даже распределитель кучи не может быть менее эффективным.

  2. распределитель, который предоставляет память, выровненную, скажем, 64bytes (подходит для выровненной загрузки в регистры AVX).

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

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


Примечание добавлено в ответ на дополнительные вопросы.

статьи Мы датируется 2008 годом и не применяется к современной практике C++ (с использованием стандарта C++11 или более поздней версии), когда управление памятью с помощью std контейнеры и умные указатели (std::unique_ptr и std::shared_ptr) избегает утечек памяти, которые являются основным источником увеличения спроса на память в плохо написанном коде.

при написании кода для определенных конкретных приложений могут быть веские причины использовать пользовательский распределитель -- и стандартная библиотека C++ поддерживает это, поэтому это законный и соответствующий подход. Веские причины включают те перечисленные уже выше, в частности когда высокая эффективность необходима в многопоточной окружающей среде или быть достиганным через инструкции SIMD.

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


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

вы можете посмотреть на некоторые коллекции std, чтобы увидеть, как они позволяют программистам предоставлять указанный распределитель, такой как std:: vector. Они используют необязательный аргумент шаблона, чтобы пользователь мог выбрать класс распределителя. Он также позволяет передать распределитель в качестве примера, если вы хотите. Если вы этого не сделаете, он создает экземпляр с конструктором по умолчанию. Если вы не выбирайте класс распределителя, тогда он использует распределитель по умолчанию, который просто использует кучу. Ты можешь сделать то же самое.

template<typename C, typename Allocator = std::allocator<C> >
class B {

   vector<C, Allocator> bField;

   void generateCs() {  
     std::vector<CPrototype> prototypes = getPrototypes();
     for(std::size_t n=0; n < prototypes.size(); n++) {
         //construct real object  (CPrototype->C)
         bField.push_back( makeItBorn(prototypes[n]) ); 
     }
   }

   B(const Allocator& allo = Allocator()) : bField(allo) {
       generateCs();
   }
}

Это позволяет пользователю контролировать распределение, когда они хотят, но они также игнорируют его, если им все равно