#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