Как перезаписать поведение метода construct по умолчанию в классе allocator в C STL

#c #stl

#c #stl

Вопрос:

Как перезаписать поведение метода construct по умолчанию в классе allocator в STL? Похоже, что следующее не работает:

 #include <list>
#include <iostream>
#include <memory>

struct MyObj {
    MyObj() {
        std::cout << "This is the constructor" << std::endl;
    }
    MyObj(const MyObjamp; x) {
        std::cout << "This is the copy constructor" << std::endl;
    }
};

class MyAlloc : public std::allocator <MyObj>{
public:
    void construct(pointer p, const_reference t){
        std::cout << "Construct in the allocator" << std::endl;
        new( (void*)p ) MyObj(t);
    }
};

int main(){
    MyObj x;         
    std::list <MyObj,MyAlloc> list(5,x);
} 
  

Эта программа возвращает

 This is the constructor
This is the copy constructor
This is the copy constructor
This is the copy constructor
This is the copy constructor
This is the copy constructor
  

Я хотел бы, чтобы он возвращал

 This is the constructor
Construct in the allocator
This is the copy constructor
Construct in the allocator
This is the copy constructor
Construct in the allocator
This is the copy constructor
Construct in the allocator
This is the copy constructor
Construct in the allocator
This is the copy constructor
  

Ответ №1:

Добро пожаловать в удивительный мир распределителей. Я надеюсь, вам понравится ваше пребывание, хотя это маловероятно.

Правило № 1: Не производите от std::allocator . Если вы хотите использовать свою собственную схему распределения, то напишите свой собственный распределитель. Если вы хотите «переопределить» некоторые функциональные возможности в std::allocator, тогда просто создайте std::allocator экземпляр и вызовите его функции в неперекрываемых функциях.

Обратите внимание, что вывод на самом деле все равно не работает. В C 03 распределителям не разрешается иметь состояние, а указатель на v-таблицу считается состоянием. Таким образом, распределители не могут иметь виртуальных функций. Вот почему std::allocator не имеет виртуальных функций.

Правило №2: std::list<T> никогда не выделяет T объекты. Помните: std::list это связанный список. Он выделяет узлы, которые имеют T в качестве члена. Это делается с помощью некоторой магии шаблона, где он вызывает ваш класс iterator, используя в качестве параметра тип внутреннего узла, и который возвращает новый объект allocator того же шаблона, но с другим параметром шаблона.

Это делается с помощью элемента структуры шаблона вашего вызова allocator rebind , который имеет вызываемый элемент typedef other , который определяет новый тип allocator. В вашем случае std::list сделает это:

 MyAlloc::rebind<_ListInternalNodeType>::other theAllocatorIWillActuallyUse();
  

И это все еще обеспечивается базовым классом. Итак, тип MyAlloc::rebind<_ListInternalNodeType>::other является std::allocator<_ListInternalNodeType> . Какой тип распределителя std::list будет использоваться для фактического распределения объектов.

Комментарии:

1. Итак, @NicolBolas сказал «не выводить из std::allocator «, но приведенный ниже ответ @Nawaz делает именно это. Является ли это допустимым, поддерживаемым действием, или теоретически вселенную можно превратить в желе со вкусом ростков, стоит ли мне попробовать это? Игнорируйте, что виртуальная отправка недоступна (поскольку при предоставлении производного типа в качестве аргумента шаблона базовый тип несуществен и виртуальная отправка не нужна) и игнорируйте конструктивные достоинства деривации как механизма наследования реализации.

Ответ №2:

Вам нужно сделать немного больше, чем то, что вы делаете в своем коде. Это минимальный код, который необходим для того, чтобы заставить его работать так, как вы хотите:

 template<typename T>
class MyAlloc : public std::allocator <T>
{
public:
     typedef size_t     size_type;
     typedef ptrdiff_t  difference_type;
     typedef T*         pointer;
     typedef const T*   const_pointer;
     typedef Tamp;         reference;
     typedef const Tamp;   const_reference;
     typedef T          value_type;


     template<typename U>
     struct rebind
     {
       typedef MyAlloc <U> other; 
     };

     MyAlloc() {}

     template<typename U>
     MyAlloc(const MyAlloc<U>amp;) {}

     void construct(pointer p, const_reference t){
        std::cout << "Construct in the allocator" << std::endl;
        new( (void*)p ) MyObj(t);
     }

};
  

А затем использовать его как:

    int main(){
    MyObj x;         
    std::list <MyObj,MyAlloc<MyObj> > list(5,x);
}
  

Вывод (по вашему желанию):

 This is the constructor
Construct in the allocator
This is the copy constructor
Construct in the allocator
This is the copy constructor
Construct in the allocator
This is the copy constructor
Construct in the allocator
This is the copy constructor
Construct in the allocator
This is the copy constructor
  

Онлайн-демонстрация:http://www.ideone.com/QKdqm

Вся идея этого минимального кода заключается в переопределении определения rebind class template в базовом классе std::allocator , который определяется как:

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

когда на самом деле нам это нужно:

 template<typename U>
struct rebind
{
    typedef MyAlloc<U> other; 
};
  

Потому что в конечном итоге это rebind<U>::other то, что используется в качестве распределителя.

Кстати, typedefs необходимы для переноса имен (типов) в область производного класса (по умолчанию они не видны, поскольку MyAlloc теперь это шаблон класса). Итак, вы могли бы записать это как:

 template<typename T>
class MyAlloc : public std::allocator <T>
{
    typedef std::allocator <T> base;
public:
     typedef typename base::size_type        size_type;
     typedef typename base::difference_type  difference_type;
     typedef typename base::pointer          pointer;
     typedef typename base::const_pointer    const_pointer;
     typedef typename base::reference        reference;
     typedef typename base::const_reference  const_reference;
     typedef typename base::value_type       value_type;

     //same as before
 };
  

Результат был бы таким же:http://www.ideone.com/LvQhI