Почему этот C ++ STL-распределитель не выделяет? - PullRequest
12 голосов
/ 12 февраля 2009

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

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;
}

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

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

Ответы [ 4 ]

6 голосов
/ 12 февраля 2009

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

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

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

4 голосов
/ 12 февраля 2009

В этом случае проблема заключается в том, что я не переопределил элемент повторного связывания распределителя. Эта версия работает (в 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 прав в том, что этого не следует делать таким образом.

2 голосов
/ 12 февраля 2009

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

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);
    }
};

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

    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 :: string нужно использовать следующую конструкцию при объявлении переменных этого типа (это проще всего с typedef):

std::basic_string<char>, std::char_traits<char>, MY_NAMESPACE::allocator<char> >
1 голос
/ 12 февраля 2009

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

#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;
}

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

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