KnigaRead.com/

Albert Makhmutov - Идиомы и стили С++

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Albert Makhmutov, "Идиомы и стили С++" бесплатно, без регистрации.
Перейти на страницу:

Вспомните Шаг 12. Что делается, когда компилятор видит ключевое слово new? Сначала выделяется память, и компилятор получает указатель на нее, потом вызывается конструктор; ясно, что где-то в глубине кода конструктор получает указатель на свежую, сырую память, исполняет себя, а потом возвращает указатель обратно клиенту. Как выделяется сырая память? На то есть оператор operator new.

Вот его прототип:

void* operator new(size_t);

Ничего сложного. Несколько необычно получает параметр, правда, но это только на первый взгляд, на то он и оператор. Параметр - это размер объекта; похоже на то, как если бы мы написали (но неверно разумеется):

CClass* cc = operator new(sizeof(CClass));

Зато мы можем перегрузить operator new (что не удивительно), да к тому же добавить параметров (что уже лучше). Главное, чтобы первый параметр был все равно size_t - размер объекта, а то из спецификаци языка вылетим. Обратите внимание - перегружается оператор operator new(), а не ключевое слово new, оно же элемент синтаксиса, можно сказать. Прототип перегрузки и пример использования:

void* operator new(size_t, int);

// V

// --‹-‹-‹---

// ¦

// V

CClass* cc = new(234) CClass;

У меня ни малейшего понятия, зачем Вы суете сюда int. Лично у меня на параметр свои виды. Я собираюсь пульнуть туда как раз тот указатель, который надо вернуть. А больше ничего не делать. Чтоб память НЕ ВЫДЕЛЯЛАСЬ. Потому что я намереваюсь ее раздобыть сам.

// Прототип оператора

void* operator new(size_t, void* buffer) {return buffer}

// Использование. Получаем шмат памяти

char* piece_of_memory[1000000000000];

// делаем вызов.

CClass* cc = new(piece_of_memory) CClass;

Мрачная картина. Душераздирающее зрелище. Но результат на лице: Это - менеджер памяти. Самый натуральный. Буферизованный оператор operator new. Виртуальный конструктор (не знаю почему; учитывая, что в официальных справочниках Microsoft разъясняется, что СИСТЕМНЫЙ диск это тот, с которого ЗАГРУЖАЕМСЯ, а ЗАГРУЗОЧНЫЙ тот, на котором СИСТЕМА, меня мало что удивляет в терминологиях).

Шаг 19 - Управление памятью. Продолжение 1.

Бог: "Я стер всякую жизнь. Впочем, я ничего не уничтожил. Я просто воссоединил в Себе частицы Себя. У меня на планете было множество типов с безумными глазами, которые болтали насчет слияния со Мной. Вот они и слились."

Кармоди: "Им это понравилось?"

Бог: "Откуда я знаю?"

Р. Шекли. Координаты чудес.

А что случается, когда компилятор видит ключевое слово delete? А кстати то же самое, только в обратном порядке. Сначала вызывает деструктор, потом вызывает operator delete(), прототип коего:

void operator delete (void* to_free_mem);

Параметр есть указатель на освобождаемую память. Да, но в нашем примере НЕ НАДО ничего освобождать. Мы сами добыли память, сами и освободим. Что делать? Вообще не употреблять delete (ключевое слово, не оператор). А употребить только деструктор.

ourObject-›~CClass();

Но с другой стороны, не следует навечно занимать память.Это нехорошо. Отдавать нужно так же, как и брали. Брали malloc() - отдаем через free(). Брали в стеке - ничего не делаем, само освободится. А может, брали через operator new() - тогда освобождаем через operator delete(). Вы наверное поняли, что сырую память можно взять через чистый оператор operator new():

// взяли память

char* piece_of_memory = operator new(100000000);

// положили на место.

operator delete (piece_of_memory);

Вроде управились со всем. Надо только запомнить, что всегда (превсегда) выделение и освобождение памяти должно идти только через комплементарные пары функций и механизмов. Потому что они (механизмы) совершенно друг друга не понимают. И память, выделенная через malloc, с точки зрения пары new-delete совсем не выделенная. А удаленная через free не удаленная. И наоборот. Полная несовместимость во все стороны.

А что нам проку от управления памятью, спросите Вы? Да хотя бы скорость. Когда выполняется operator new(), программа в общем случае обращается к операционной системе. Операционка, как Вы понимаете, не в восторге от толкающихся вокруг нее процессов и потоков, наперебой просящих у нее кусочки памяти, и выдает память в порядке очереди, к тому же у нее есть любимчики, да еще и себе нужно оставить… Ведет себя в точности так же, как нормальный начальник компьютерного отдела при распределении новой техники. Так что выгоднее сразу хапнуть достаточное количество памяти, а потом самостоятельно ее раздавать объектам.

А что касается освобождения памяти в нашем примере, то это вообще ураган: не нужно разрушать объекты по отдельности; Вы просто хрясь! - и освобождаете буфер целиком. Интересно, что чувствуют при этом объекты?

Конечно, нужно немного усложнить код, чем наши жалкие две строки. Следует "шмат памяти" оформить в виде класса, так чтобы выдавать память объектам последовательно. У объектов перегрузить операторы operator new() так, чтобы память бралась где нам надо, и operator delete() так чтобы он ничего не делал. И "шмат" называть "пулом". А то не поймут.

Я лишь чуть-чуть усложняю класс, только чтоб показать.

#include ‹stdlib.h›

// Класс пула

class CPool {

public:

 static char buffer[8096]; // статический буфер

 static char* position; // текущая позиция

 static void* getSomeMemory(size_t); // получить немного памяти

};


// вот получаем немного памяти.

void* CPool::getSomeMemory(size_t bytes) {

 void* ret_val = position; // вернуть надо текущую позицию.

 position+=bytes; // а счетчик увеличить

 return ret_val;

}


// Это так… эксперимент.

// Класс с собственным управлением памятью.

class CThat {

private:

 int m_some_number; // не знаю что.

public:

 // перегруженные operator new, operaton delete

 void* operator new(size_t bytes) { return CPool::getSomeMemory(bytes); }

 void operator delete(void*) {}

};


// инициализация статических членов.

char CPool::buffer[8096];

char* CPool::position = CPool::buffer;

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

Несколько строк занудства.

Операционка неохотно берет себе память обратно. Возможно, освобожденный фрагмент вообще останется в ведении менеджера памяти самой программы, до ее завершения. Но конечно крупные куски она заглатывает тут же. Если возитесь с мелочью, проверьте этот момент на всякий случай; нет ничего приятнее свопа или уборки мусора в нужное время!

Глобальный оператор ::operator new() и глобальный оператор ::operator delete() не трогайте. Проще и намного умнее перегружать операторы в классах.

new, operator new и конструктор, а так же delete, operator delete и деструктор - АБСОЛЮТНО разные вещи. Как мы уже выяснили, их можно вызывать по отдельности. Не давайте себя запутать, если кто-нибудь будет говорить об операторе new - такого не бывает, бывает или оператор operator new(), или ключевое слово new.

Шаг 20 - Временные объекты. Неявные вызовы конструкторов и их подавление.

Не удается углубиться в какую-либо тему. Приходится касаться по верхам, потом переключаться на что-то другое. С другой стороны, может это и правильно, часто достаточно только знать, что есть ТАКОЕ решение, а изучить детально можно и позже, когда сделаешь окончательный выбор. Да и не очень это интересно - что за радость переписать двадцать страниц из учебника, или перевести статью какого-нибудь доктора CS? Объяснения которого в точности так же логичны, как рассказ Ивана Бездомного насчет "…Берлиоза зарезало трамваем, а тот заранее знал про масло, которое Аннушка пролила" - то есть логика и связь есть - но только для него самого.

Чтож, к делу.

А кто такие временные объекты? Локальные переменные с замечательными именами a, a1, a2, a_1, tmp1, tmp2? (Кстати ни за что не берите на работу болванов, которые так именуют переменные; пусть на FoxPro пишут. Думаю написать про это отдельный Шаг - причины для немедленного увольнения.) Вообще-то нет. Временные объекты - это объекты, которые не имеют имен в коде и неявно создаются компилятором. Поскольку неявные "подарки" компилятора иногда бывают очень некстати, лучше заранее знать, чего можно ожидать от него. А зачем он их создает? Первое - при выполнении преобразования типов, для вызова функций. Второе - для возвращения объекта из функции.

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*