Перегрузка операции присваивания




Перегрузка унарных операций

Первый вариант перегрузки унарных операций состоит в том, что унарная функция-операция, определяемая в классе, должна быть представлена с помощью нестатического метода, не имеющего никаких параметров. При этом операндом является вызвавший её объект.

Пример 1. Перегрузка унарной операции с помощью функции-операции определённой в классе. <…> class Player{ <…> Player & operator --() { --currentLvlOfHealth; return *this; } }; Player myHero; cout << (--myHero). getHealth();

Второй вариант перегрузки состоит в том, что функция-операция определяется вне класса и имеет один параметр типа этого класса.

Пример 2. Перегрузка унарной операции с помощью функции-операции, определённой вне класса. <…> class Player{ <…> friend Player & operator --(Player &Pl) }; Player &operator --(Player &Pl){ --Pl.currentLvlOfHealth; return Pl; }

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

Добавим в класс дополнительный метод изменяющий количество очков игрока.

Пример 3. Перегрузка унарной операции с помощью функции, не описанной как дружественной внутри класса. class Player{ <…> public: void changePoints(int NOP){ numberOfPoints = NOP; } }; /*Теперь можно перегрузить операцию инкремента с помощью обычной функции, описанной вне класса*/ Payer & operator ++(Player &Pl){ int NP = Pl.getPoints(); NP++; Pl.changePoints(NP); return Pl; }

Перегрузка операций постфиксного инкремента и декремента

Операции постфиксного инкремента и декремента должны иметь первый параметр типа int. Он, как правило, используется для того, чтобы их можно было отличить от префиксной формы записи.

Пример 4. Перегрузка унарной постфиксного декремента. <…> class Player{ <…> Player & operator --(int) { Player Pl(*this); currentLvlOfHealth--; return Pl; } }; Player myHero; cout << (myHero--).getHealth();

Перегрузка бинарных операций

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

Пример 5. Перегрузка бинарной операции с помощью функции-операции, определённой в классе. <…> class Player{ <…> booloperator > (constPlayer &Pl){ if(currentLvlOfHealth > maxHealth) return true; return false; } };

Второй вариант предполагает то, что функция-операция определяется вне класса. В таком случае она должна иметь два параметра типа класса.

Пример 6. Перегрузка бинарной операции с помощью функции-операции, определённой вне класса. booloperator > (constPlayer &Pl1, constPlayer &Pl2){ if(Pl1.getHealth() > Pl2.getHealth()) return true; return false; }

Перегрузка операции присваивания

Операция присваивания определена в любом классе по умолчанию в качестве поэлементного копирования. Она вызывается каждый раз, когда одному существующему экземпляру класса присваивается значение другого. В том случае если класс содержит поля, под которые динамически выделяется память, необходимо определить собственную операцию присваивания. Для сохранения семантики присваивания, функция-операция всегда должна возвращать ссылку на экземпляр класса, для которого она вызвана. При этом она должна принимать в качестве параметра всего один аргумент – ссылку на присваиваемый объект.

Операцию присваивания можно определять исключительно в качестве метода класса. Также она не наследуется.

Пример 7. Перегрузка операции присваивания. class Player & operator = (const Player &Pl){ /*проверка на самоприсваивание*/ if (&Pl == this) return *this; currentLvlOfHealth = Pl.currentLvlOfHealth; presenceOfWeapins = Pl.presenceOfWeapins; impactForce = Pl.impactForce; }

Перегрузка операций new и delete

Для обеспечения альтернативного варианта управления памятью, существует возможность определения собственных операций new и new[], предназначенных для выделения динамической памяти под экземпляр класса и массив объектов соответственно, и delete и delete[], предназначенных для её освобождения.

Существует ряд правил при создании функций-операций, используемых для перегрузки new и delete.

    1. Таким функциям-операциям не требуется передавать параметр типа класса.
    2. В качестве первого параметра функциям new и delete должен передаваться размер объекта типа size_t, возвращаемый функцией sizeof и при вызове передаваемый в качестве неявного параметра.
    3. Такие функции-операции должны быть определены с типом возвращаемого значения void*, даже если return возвращает указатель на иные типы данных.
    4. Операция delete должна иметь тип возвращаемого значения void и первый аргумент типа void*.
    5. Операции выделения и освобождения памяти являются статическими элементами класса.

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

Операция new выполняет следующие действия:

        • возвращение правильного значения;
        • корректная обработка запроса на выделение памяти нулевого размера;
        • порождение исключения при невозможности выполнения запроса.

Операция delete выполняет безопасное удаление нулевых указателей.

Для обеспечения описанного действия в операции delete внутри неё необходима проверка указателя на нуль или отсутствие каких-либо действиях в случае равенства.

Стандартные операции выделения и освобождения памяти могут использоваться в области действия класса наряду с перегруженными с помощью операции «::» для объектов этого класса и любых других.

Перегрузка операции выделения памяти применяется для экономии памяти, повышения быстродействия программы или размещения данных в некоторой конкретной области.

Пример 8. Перегрузка операции new. class Object { <…> }; class PtrObject{ <…> private: Object *ptr; }; PtrObject *ptrPO = new PtrObject;

При выделении участка памяти под объект типа PtrObject с помощью стандартной операции new фактический объём памяти будет превышать тот, что возвращает sizeof(PtrObject). Чтобы операция delete отрабатывала правильно, new, как правило, записывает в начало выделяемого участка памяти его размер.

С целью экономии памяти можно определять собственную операцию new для класса PtrObject, которая будет выделять блок памяти достаточного размера, после чего будет выполнять размещение указателей на Object.

Теперь добавим в класс, описанный в примере 8, статическое поле, указывающее на начало выделенного блока памяти. Идентификатор такого поля – headOfFreeSpace. Прочие неиспользуемые ячейки определим списком. Для того, чтобы не занимать память под поле связи будем использовать объединение, которое позволит использовать одну и ту же ячейку памяти для размещения либо указателя на объект, либо указателя на следующую ячейку памяти.

Пример 9. Перегрузка операции new. Развитие примера 8. class PtrObject{ <…> private: Object *ptr; union{ Object *ptrO; //Указатель на сам объект PtrObject *next; /*Указатель на следующую ячейку памяти*/ }; static const int sizeOfBlock; //Размер блока памяти static PtrObject * headOfFreeSpace; public: static void * operator new(size_t size); }; void *PtrObject:: operator new(size_t size){ /*Отказ запросам с количеством памяти несоответствующим размеру PtrObject и их перенаправление стандартной операции new*/ if (size!= sizeof(PtrObject)) return PtrObject:: operator new(size); PtrObject *ptrO = headOfFreeSpace; /*Смещение указателя списка свободных ячеек.*/ if (ptrO) headOfFreeSpace- ptrO->next; /*Если свободная память отсутствует, необходимо выделение блока памяти.*/ else{ PtrObject *newBlock = static_cast< PtrObject*> (PtrObject:: operator new (sizeOfBlock * sizeof(PtrObject))); for (int i = 0; I < sizeOfBlock-1; i++) newBlock[i].next = &newBlock[i+1]; newBlock[sizeOfBlock-1].next = 0; /*Установка начала списка свободных ячеек.*/ headOfFreeSpace = &newBlock[i]; ptrO = newBlock; } return ptrO; /*Возвращение указателя на выделенную память*/ }

Так как перегруженная операция new наследуется, она вызывается для всех дочерних объектов. В том случае если размер дочерних объектов не соответствует размеру базового, могут появиться непредвиденные ошибки, избежать которые можно, установив проверку на соответствие размеров. Её можно сформулировать следующим образом: «если размер экземпляра класса не равен тому, для которого была перегружена операция new, запрос на выделение участка памяти должен быть передан стандартной операции new ».

 

В программе, которая использует класс PtrObject, обязательно должна быть инициализация его статических полей.

Пример 10. Инициализация статических полей. PtrObject *PtrObject:: headOfFreeSpace; const int PtrObject:: sizeOfBlock = 1024;

Если операция new перегружена, то же самое должно быть выполнено и для операции delete.

В соответствии с примерами выше, делаем вывод о том, что перегруженная операция delete должна добавлять освобождённую ячейку памяти к общему списку свободных ячеек.

Пример 11. Перегрузка операции delete. class PtrObject{ <…> private: <…> static void operator delete(void*freeSpace, size_t size){ /*Проверка на соответствие размеров*/ if(freeSpace == 0) return; if(size!= sizeOf(PtrObject)){ PtrObject:: operator delete(freeSpace); return; } PtrObject *ptrO = static_cast<PtrObject>(freeSpace); ptrO->next = headOfFreeSpace; headOfFreeSpace = ptrO; } }; PtrObject *ptrPO = new PtrObject;

Как видите из примера, реализация проверки соответствия размеров в операции delete аналогична таковой в операции new.



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2020-06-05 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: