2009-02-12 3 views
12

Я пытаюсь написать специализированный распределитель STL, который является производным от std::allocator, но почему-то все звонки на allocate() идут в базовый класс. Я сузил его до этого кода:Почему этот распределитель S ++ STL не выделяется?

template <typename T> class a : public std::allocator<T> { 
public: 
    T* allocate(size_t n, const void* hint = 0) const { 
     cout << "yo!"; 
     return 0; 
    } 
}; 

int main() 
{ 
    vector<int, a<int>> v(1000, 42); 
    return 0; 
} 

Я ожидаю, что «Yo!» чтобы печатать, а затем какая-то ужасная ошибка, потому что я фактически ничего не выделяю. Вместо этого программа работает нормально и ничего не печатает. Что я делаю не так?

Я получаю те же результаты в gcc и VS2008.

+0

Связанные см [Почему не наследовать от станда :: распределителя ] (HTTP: // StackOverflow.ком/кв/21081796). – jww

ответ

6

Вам нужно будет предоставить шаблон элемента повторной привязки и другие элементы, перечисленные в требованиях распределителя в стандарте C++. Например, вам нужен конструктор копирования шаблона, который принимает не только allocator<T>, но также и allocator<U>. Например, один код может сделать, которым станд :: список, например, скорее всего, сделать

template<typename Allocator> 
void alloc1chunk(Allocator const& alloc) { 
    typename Allocator::template rebind< 
     wrapper<typename Allocator::value_type> 
     >::other ot(alloc); 
    // ... 
} 

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

+0

Хороший вопрос о том, как читать фактические требования к интерфейсу - в противном случае вы никогда не узнаете * наверняка *, что вы обрабатываете все, что должен делать распределитель. –

1

Следующий код печатает «лет», как ожидалось, - то, что вы видели, было нашим старым другом «неопределенное поведение».

#include <iostream> 
#include <vector> 
using namespace std; 

template <typename T> class a : public std::allocator<T> { 
public: 
    T* allocate(size_t n, const void* hint = 0) const { 
     cout << "yo!"; 
     return new T[10000]; 
    } 
}; 

int main() 
{ 
    vector<int, a<int> > v(1000, 42); 
    return 0; 
} 

Редактировать: Я только что проверил Стандарт C++ относительно распределителя по умолчанию. Нет никакого запрета на наследство от него. На самом деле, насколько мне известно, такого запрета в какой-либо части Стандарта нет.

+0

Это не сработало для меня. VS 2008. –

+0

Вы проверили этот код? Он не может работать, поскольку это в основном то же самое, что и вопрос. Я просто запускаю его в режиме отладки и выпуска, и он не работает. – Klaim

+0

Работает отлично для меня с i686-apple-darwin8-g ++ - 4.0.1 –

4

В этом случае проблема в том, что я не переопределил элемент повторной привязки распределителя. Эта версия работает (в VS2008):

template <typename T> class a : public std::allocator<T> { 
public: 
    T* allocate(size_t n, const void* hint = 0) const { 
     cout << "yo!"; 
     return 0; 
    } 

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

int main() { 
    vector<int, a<int>> v(1000, 42); 
    return 0; 
} 

Я нашел это отладкой через заголовки STL.

Независимо от того, будет ли это работать или нет, это будет полностью зависеть от реализации STL, поэтому я считаю, что в конечном счете, Klaim прав, поскольку это не должно быть сделано таким образом.

+1

Фактически диспетчеризация виртуальных функций не используется для вызовов распределителя, так как тип известен во время компиляции, поэтому я считаю, что это должно всегда работать. Единственная причина не беспокоиться о наследовании от std :: allocator - это потому, что на самом деле это не спасает вас от многократного ввода! –

+0

... но см. Ответ litb на другое требование (шаблон copy ctor), который вам нужно реализовать. –

2

У меня есть два шаблона для создания настраиваемых распределителей; первые работы автомагически, если он используется на тип пользовательского:

template<> 
class std::allocator<MY_TYPE> 
{ 
public: 
    typedef size_t  size_type; 
    typedef ptrdiff_t difference_type; 
    typedef MY_TYPE* pointer; 
    typedef const MY_TYPE* const_pointer; 
    typedef MY_TYPE& reference; 
    typedef const MY_TYPE& const_reference; 
    typedef MY_TYPE  value_type; 

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

    pointer allocate(size_type n, std::allocator<void>::const_pointer hint = 0) 
    { 
     return reinterpret_cast<pointer>(ALLOC_FUNC(n * sizeof(T))); 
    } 
    void construct(pointer p, const_reference val) 
    { 
     ::new(p) T(val); 
    } 
    void destroy(pointer p) 
    { 
     p->~T(); 
    } 
    void deallocate(pointer p, size_type n) 
    { 
     FREE_FUNC(p); 
    } 
    size_type max_size() const throw() 
    { 
     // return ~size_type(0); -- Error, fixed according to Constantin's comment 
     return std::numeric_limits<size_t>::max()/sizeof(MY_TYPE); 
    } 
}; 

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

namespace MY_NAMESPACE 
    { 

    template <class T> class allocator; 

    // specialize for void: 
    template <> 
    class allocator<void> 
    { 
    public: 
     typedef void*  pointer; 
     typedef const void* const_pointer; 
     // reference to void members are impossible. 
     typedef void  value_type; 

     template <class U> 
     struct rebind 
     { 
      typedef allocator<U> other; 
     }; 
    }; 

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

     template <class U> 
     struct rebind 
     { 
      typedef allocator<U> other; 
     }; 

     allocator() throw() 
     { 
     } 
     template <class U> 
     allocator(const allocator<U>& u) throw() 
     { 
     } 
     ~allocator() throw() 
     { 
     } 

     pointer address(reference r) const 
     { 
      return &r; 
     } 
     const_pointer address(const_reference r) const 
     { 
      return &r; 
     } 
     size_type max_size() const throw() 
     { 
      // return ~size_type(0); -- Error, fixed according to Constantin's comment 
      return std::numeric_limits<size_t>::max()/sizeof(T); 
     } 
     pointer allocate(size_type n, allocator<void>::const_pointer hint = 0) 
     { 
      return reinterpret_cast<pointer>(ALLOC_FUNC(n * sizeof(T))); 
     } 
     void deallocate(pointer p, size_type n) 
     { 
      FREE_FUNC(p); 
     } 

     void construct(pointer p, const_reference val) 
     { 
      ::new(p) T(val); 
     } 
     void destroy(pointer p) 
     { 
      p->~T(); 
     } 
    }; 

template <class T1, class T2> 
inline 
bool operator==(const allocator<T1>& a1, const allocator<T2>& a2) throw() 
{ 
    return true; 
} 

template <class T1, class T2> 
inline 
bool operator!=(const allocator<T1>& a1, const allocator<T2>& a2) throw() 
{ 
    return false; 
} 

} 

первого шаблон выше, для собственного определенного типа, не требует дальнейшей обработки, но используется автоматически стандартными классами контейнеров. Второй шаблон требует дальнейшей работы при использовании стандартного типа. Для станда :: строки, например, один должны использовать следующую конструкцию при объявлении переменных этого типа (это проще всего с ЬурейиМ):

std::basic_string<char>, std::char_traits<char>, MY_NAMESPACE::allocator<char> > 
+0

Похоже, 'max_size()' должен возвращать 'std :: numeric_limits :: max()/sizeof (MY_TYPE)'. – Constantin

+0

@ Константин: Очень хорошее наблюдение, спасибо большое! Я буду обновлять код соответствующим образом. :-) –

Смежные вопросы