Страницы

Поиск по вопросам

пятница, 10 января 2020 г.

Использование аллокаторов

#cpp


Что представляет собою макрос __STL_DEFAULT_ALLOCATOR(T), а так же макрос __STL_NOTHROW.
И - самое главное -  как ими пользоваться?
Я - в качестве эксперимента - пытаюсь написать "свой собственный" "вектор" и выделить
для него память при помощи аллокатора. А точнее просто пытаюсь понять, как пользоваться
готовым библиотечным аллокатором?  МОЙ ВОПРОС : В векторе не работают конструкторы.
Не знаю почему. Привожу код.

template >
class Vec{
    T* bbb ;
    T* eee ;
    T* mmm ;
    const A* a ;
public :

    Vec(const A & aaa = A())
        :bbb(nullptr),eee(nullptr),mmm(nullptr)
        ,a(&aaa)
        // Здесь всё нормально.
    {}

    Vec( size_t n  ,  const A & aaa = A() )
        :a(&aaa)
    {
        bbb = a->allocate(n) ;
        // Тут что-то не так.
        // Без этой строки - ошибка вермени выполнения.

        uninitialized_fill_n(bbb,n,T()) ;
        eee = bbb ;
        mmm = bbb + n ;
    }

    Vec( int n  , T t , const A & aaa = A())
        :a(&aaa)
    {
        uninitialized_fill_n(bbb,n,t) ;
        eee = bbb + n ;
        mmm = bbb + n  ;
        // Не работает.
    }

    Vec(const Vec< T, A> & vc)
    {
        mmm = uninitialized_copy(vc.bbb,vc.mmm,bbb) ;
        eee = uninitialized_copy(vc.bbb,vc.eee,eee) ;
        // Не проверял.
    }


Я работаю в Visual C++ 2010 Express.
Мне не понятно, - как аллокатор выделяет память внутри сигнатуры конструктора.

ОБНОВЛЕНИЕ :
Здравствуйте уважаемый ixSci! Прошу прощение за слишком краткое описание проблемы.
Мне казалось, что и так будет всё понятно.

ИТАК. Без строки a->allocate(n) возникает ошибка времени выполнения. При наличии
данной строки Компилятор пишет следующее:


  error C2663: 'std::allocator<_Ty>::allocate' : 2 overloads have no legal conversion
for 'this' pointer


Это сообщение происходит ТОЛЬКО при использовании данного конструктора. То есть,
когда в исполняемом коде возникает объект, использующий этот конструктор. Например:
Vec vc(5).

Теперь о моей компетенции. Я прочитал Г.Шилдт"Базовый курс С++" и заканчиваю изучать
учебник С.Прата"Язык программирования С++ Лекции и упражнения". Опыт, конечно, небольшой.
Но по меткому выражению Мэтью Уилсона, - "очень люблю заглядывать под капот." Кстати,
в первом коде, который Вы мне прислали есть ошибка: Функция construct() имеет второй
аргумент - ссылку на значение инициализирующего объекта. Без этого аргумента функция
не работает. Есть и другие ошибки. Но в целом код даёт понимание, - как работает Аллокатор.
Так что всё равно - СПАСИБО! Я добавил в Ваш код некоторые свои комментарии. Если хотите,
я покажу то, что у меня получилось. И ЕЩЁ. Если можно, покажите, пожалуйста как должен
выглядеть "НОРМАЛЬНЫЙ" вектор в котором память выделяется при помощи Аллокатора. Только
"шапку", поля данных и ОДИН конструктор, создающий объект из "n" элементов и их инициализирующий.
СПАСИБО!  

ОБНОВЛЕНИЕ 2 Уважаемые господа @ixSci и @Abyx ! Огромное СПАСИБО! Благодаря Вам я
познакомился с Аллокаторами. Теперь я пришёл к ряду выводов. Хочется узнать, - верны
ли они??? 

ВЫВОДЫ:


Аллокатор как параметр присутствует во всех конструкторах библиотечного контейнера
vector только для инициализации его, - вектора, - базового класса, в котором и хранится
Аллокатор. Мои конструкторы вектора не работали именно потому, что в них Аллокатор,
уже объявленный в шаблоне класса, - повторно объявлялся и инициализировался.
Если удалить из памяти объекты при помощи методаdeallocate(), предварительно не разрушив
их при помощи методаdestroy(), то эти объекты могут так и остаться в куче, что вызовет
утечку памяти.
Закрытые поля данных m_Start,m_Finish,m_Storage следует помещать в конце объявления
класса. Очевидно для того, чтобы Аллокатор быстрее построил Вектор и отключился.


У меня остались вопросы.


Зачем оставлять объекты в куче, в то время, как доступа к этим объектам уже не существует?
То есть, почему бы не объединить destroy() и deallocate() в один метод?
Зачем в действительности поля m_Start,m_Finish,m_Storahe помещаются в конец объявления
Вектора? Притом, что если эти поля поместить в начало Объявления Вектора, всё и так
будет работать?

    


Ответы

Ответ 1



__STL_DEFAULT_ALLOCATOR(T) и __STL_NOTHROW это какие-то макросы, которые та или иная реализация библиотеки C++ использует. Они совершенно не нужны, для использования аллокатора. Вот Вам пример использования стандартного, библиотечного аллокатора с комментариями: #include struct A { int a = 0; int b = 1; int c = 2; }; int main() { std::allocator allocator; size_t howMuch = 50; // Выделяем память под 50 структур A. Возвращённый указатель, указывает на неинициализированную память. A* allocatedArray = allocator.allocate(howMuch); A* allocatedItem = allocator.allocate(1); // Т.к. память не инициализирована, нам нужно её инициализировать. Для этого вызываем конструктор A явно allocator.construct(allocatedItem); // Для инициализации памяти под массив есть вспомогательная функция, которая сделает то же самое, что // делает allocator по умолчанию std::uninitialized_fill_n(allocatedArray, howMuch, A()); // После того, как наигрались с объектом, его нужно удалить, но сначала нужно вызвать деструктор allocator.destroy(allocatedItem); for(size_t i = 0; i < howMuch; ++i) allocator.destroy(allocatedArray + i); // Теперь освобождаем выделенную память allocator.deallocate(allocatedItem, 1); allocator.deallocate(allocatedArray, howMuch); return 0; } ОБНОВЛЕНИЕ: Во-первых, не храните указатель на аллокатор в объекте, храните сам объект аллокатора. Более того, мне не ясно, зачем Вы его копируете в конструкторе. Просто создавайте объект аллокатора согласно параметру шаблона и всё. Во-вторых, у Вас есть очевидные ошибки в 2-х конструкторах: Vec(int n, T t, const A & aaa = A()) :a(&aaa) { uninitialized_fill_n(bbb, n, t); eee = bbb + n; mmm = bbb + n; // Не работает. } Конечно не работает, кто будет память под bbb выделять? Добавьте: bbb = a->allocate(n); Vec(const Vec< T, A> & vc) { mmm = uninitialized_copy(vc.bbb, vc.mmm, bbb); eee = uninitialized_copy(vc.bbb, vc.eee, eee); // Не проверял. } Тот же комментарий про память, а потом нужно одно копирование: Vec(const Vec< T, A> & vc) { bbb = a->allocate(vc.mmm - vc.eee); mmm = uninitialized_copy(vc.eee, vc.mmm, bbb); eee = bbb; } Ну а по поводу: // Тут что-то не так. // Без этой строки - ошибка вермени выполнения. Я не понимаю, что Вы этим хотели сказать. Что не так? Без какой строчки? В целом, у меня создалось впечатление, что в C++ Вы очень сильно плавает, а лезть в аллокаторы, плавая в C++, это гиблое дело. ОБНОВЛЕНИЕ 2: Первое, код приведённый мной абсолютно корректен. Используйте последние доступные компиляторы, а не MSVS2010, который устарел. Это же касается нижеприведённого кода - используйте MSVS2013 для его компиляции. Вот Вам минимальный пример, который содержит конструктор и деструктор для своего вектора: template> class Vector {; public: Vector() { //Выделим немного памяти, чтобы не делать это при первом же push_back m_Storage = m_Allocator.allocate(m_Capacity); } Vector(size_t size): m_Size{size}, m_Capacity{size*3/2} { //Выделим памяти немного больше, чем просили. m_Storage = m_Allocator.allocate(m_Capacity); //Инициализируем память, т.к. конструктор подразумевает созданные объекты std::uninitialized_fill_n(m_Storage, m_Size, T{}); } ~Vector() { for(auto it = m_Storage; it != m_Storage + m_Size; ++it) m_Allocator.destroy(it); m_Size = 0; m_Allocator.deallocate(m_Storage, m_Capacity); } size_t size() const { return m_Size; } private: T* m_Storage; Alloc m_Allocator; size_t m_Size = 0; size_t m_Capacity = 10; }; ОБНОВЛЕНИЕ 3 Касательно Ваших выводов: Вывод №2 неверный, deallocate освободит ранее выделенную память, но без destroy не будут вызваны деструкторы объектов, который были помещены в вектор. Вызовет ли это утечку? Это зависит от объектов, которые хранит вектор. Если это Vector, то никаких проблем не будет. Если это Vector, то память, которую string выделяет внутри себя никогда не будет освобождена. destroy обязательно должен быть вызван. Вывод №3 безосновательный. Члены класса можно помещать хоть в начало, хоть в конец. Это дело вкуса и ни на что не влияет. Просто я помещаю их в конец, я так привык. По поводу вопроса №1: Потому что у аллокатора может быть такое внутреннее устройство, что он никогда не освобождает память, а лишь конструирует/разрушает объекты. Кроме того, операции выделения памяти и конструирования это две совершенно разные операции, которые должны иметь свои контр-пары. Их объединение не имеет смысла. P.S. @NZMEY, пора прекратить эксплуатировать этот вопрос. Мы уже превращаем его в форум, а движок SO для этого не предназначен. Если будут ещё вопросы - создавайте ещё темы.

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

Отправить комментарий