Почему этот распределитель C ++ STL не выделяет?

Я пытаюсь написать собственный распределитель 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.


person Dirk Groeneveld    schedule 12.02.2009    source источник
comment
По теме см. Почему бы не унаследовать от std :: allocator.   -  person jww    schedule 13.08.2016


Ответы (4)


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

person Johannes Schaub - litb    schedule 12.02.2009
comment
Хороший момент при чтении фактических требований к интерфейсу - иначе вы никогда не узнаете наверняка, что обрабатываете все, что должен делать распределитель. - person j_random_hacker; 12.02.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, поэтому я думаю, что в конечном итоге Клайм прав в том, что этого нельзя делать таким образом.

person Dirk Groeneveld    schedule 12.02.2009
comment
На самом деле диспетчеризация виртуальных функций не используется для вызовов распределителя, поскольку тип известен во время компиляции, поэтому я считаю, что теперь это всегда должно работать. Единственная причина не беспокоиться о наследовании от std :: allocator заключается в том, что на самом деле это не избавляет вас от лишнего набора текста! - person j_random_hacker; 12.02.2009
comment
... но см. ответ litb для другого требования (ctor копирования шаблона), которое вам нужно реализовать. - person j_random_hacker; 12.02.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> >
person Stefan Rådström    schedule 12.02.2009
comment
Похоже, max_size() должен вернуть std::numeric_limits<size_t>::max() / sizeof(MY_TYPE). - person Constantin; 25.10.2010
comment
@ Константин: Очень хорошее наблюдение, большое спасибо! Соответственно обновлю код. :-) - person Stefan Rådström; 28.10.2010

Следующий код печатает «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;
}

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

person Community    schedule 12.02.2009
comment
Вы тестировали этот код? Это не может сработать, потому что это в основном то же самое, что и вопрос. Я просто запускаю его в режиме отладки и выпуска, и он не работает. - person Klaim; 12.02.2009
comment
У меня отлично работает с i686-apple-darwin8-g ++ - 4.0.1 - person Adam Rosenfield; 12.02.2009
comment
Да, я тестировал g +++ 3.4.5 - zab - person ; 12.02.2009
comment
Итак, в свете этого и открытия марвиналоном того, что ему необходимо реализовать повторное связывание ‹U›, работает ли приведенный выше код, по-видимому, зависит от того, использует ли реализация вектора ‹T› внутреннее повторное связывание. - person j_random_hacker; 12.02.2009