Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Double operator()(double,double); // прототип операции

Поиск

private:

...

};

// внешнее определение операции

double Function::operator()(double a, double b) {

...

}

// использование класса Function

Function f;

double z = f(2.5,5.6); // операционная форма вызова

double w = f.operator()(2.5,5.6); // функциональная форма вызова

Функция operator() может иметь параметры со значениями по умолчанию. Также допускается наличие в одном и том же классе нескольких перегруженных вариантов функции operator(). Их выбор осуществляется по порядку и типам параметров, как и в случае обычной перегрузки функций.


Перегрузка операций управления памятью

С++ позволяет программисту реализовать собственные варианты управления памятью и обработки ошибок путем перегрузки операций выделения и освобождения динамической памяти. Выделение памяти в С++ выполняет функция с названием operator new (не путать с операцией new), а освобождение памяти – функция operator delete (не путать с delete). Для массивов выделение и освобождение памяти выполняют соответственно функции operator new[] и operator delete[] (также не путать с new[] и delete[]). Эти функции неявно вызываются операциями new, delete, new[], delete[] для соответствующих манипуляций с памятью. Так, операция new сначала обращается к функции operator new, получает от нее указатель на выделенный блок памяти и затем вызывает конструктор класса для инициализации объекта в этом блоке. Операция delete сначала вызывает деструктор класса для деинициализации объекта, а затем обращается к функции operator delete для освобождения памяти, которую занимал объект. Похожая ситуация характерна и для new[], delete[][37].

Программисту разрешено перегружать функции operator new, operator delete, а также operator new[] и operator delete[]; перегрузка соответствующих операций new, delete, new[], delete[] не допускается. Прототипы функций operator new, operator delete, operator new[] и operator delete[] сосредоточены в заголовочном файле new.h.

Для правильной перегрузки функции выделения динамической памяти new следует придерживаться ряда правил: 1) обеспечивать правильное возвращаемое значение; 2) корректно обрабатывать выделение блока памяти нулевого и «неправильного» размера; 3) осуществлять вызов функции обработки ошибок при нехватке памяти. Функция operator new должна иметь первый параметр типа size_t и может иметь дополнительные параметры; для них даже допустимы умалчиваемые значения.

Правильное возвращаемое значение функции operator new определить легко. В случае успеха это указатель на блок выделенной памяти; в случае неудачи исключение std::bad_alloc, которое выбрасывается стандартной функцией выделения памяти. Если в функцию передается запрос на блок памяти нулевого размера, то его можно просто принудительно «превращать» в запрос блока размером 1 байт. При неправильном размере блока обработку запроса следует просто передавать стандартной функции::operator new.

Обработку ошибок в operator new можно организовать за счет переопределения стандартной функции обработки ошибок. Эта функция описана в файле new.h и определяется типом new_handler:

typedef void (*new_handler) ();

Переопределить рассматриваемую функцию можно с помощью функции set_new_handler, которая также описана в new.h и имеет следующий прототип:

extern new_handler set_new_handler(new_handler new_p);

Функции set_new_handler должен быть передан указатель типа new_handler на новую функцию-обработчик, при этом она возвратит указатель на старую функцию-обработчик.

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

// заголовочный файл

class NewHandler { // управление выделением памяти

public:

// замена обработчика ошибок

static new_handler set_new_handler(new_handler new_new_handler);

// выделение памяти

static void * operator new(size_t sz);

private:

// текущий обработчик ошибок

static new_handler current_handler;

};

// файл реализации

// обязательная инициализация статического компонента

new_handler NewHandler::current_handler;

// новый (пользовательский) обработчик ошибок

void MyNewHandlerFunc() {

Специальная обработка пользователя

throw std::bad_alloc();

}

// пользовательская функция выделения памяти

void * NewHandler::operator new(size_t sz) {

sz = sz? sz: 1; // проверяем размер на нуль

void * mem_block = 0;

// ставим новый обработчик ошибок и запоминаем указатель на старый

new_handler old_handler = set_new_handler(MyNewHandlerFunc);

try {

// пытаемся выделить память

mem_block =::operator new(sz);

// специальная обработка пользователя

...

}

catch(std::bad_alloc &) { // попытка неудачна

set_new_handler(old_handler);

// восстанавливаем старый обработчик

Throw; // передаем исключение старому обработчику

}

// восстанавливаем старый обработчик

set_new_handler(old_handler);

// возвращаем указатель на выделенный блок

return mem_block;

}

// замена обработчика ошибок

new_handler NewHandler::set_new_handler(new_handler new_new_handler) {

new_handler old_handler =::set_new_handler(new_new_handler);

current_handler = new_new_handler;

return old_handler;

}

Функция обработки ошибок может быть специфицирована явно в заголовке функции operator new. Тогда устанавливать обработчик ошибок можно сразу при вызове operator new. Ниже дано модифицированное определение класса NewHandler и функции operator new, допускающее явное задание обработчика ошибок:

class NewHandler {

public:

// замена обработчика ошибок

static new_handler set_new_handler(new_handler new_new_handler);

// выделение памяти

static void * operator new(size_t sz, new_handler pnew_handler);

private:

// текущий обработчик ошибок

static new_handler current_handler;

};

...

void * NewHandler::operator new(size_t sz, new_handler pnew_handler) {

sz = sz? sz: 1;

void * mem_block = 0;

new_handler old_handler = set_new_handler(pnew_handler);

То же, что и выше

}

Перегрузка функции operator new[] в целом аналогична перегрузке operator new. Аналогичны и правила перегрузки, однако имеется ряд жестких ограничений.

Для функции освобождения динамической памяти operator delete есть всего два правила перегрузки: 1) корректно «освобождать» память по нулевому указателю; 2) правильно реагировать на ситуацию, когда удаляемый объект имеет некорректный размер.

Вот обобщенный вариант переопределения функции operator delete:

void operator delete(void * memory) {

if(memory == 0) return; // для нулевого указателя ничего не делать

Специальная обработка пользователя

::operator delete(memory); // освободить память

...

}

Операция operator delete[] перегружается аналогично, но, как и operator new[], характеризуется жесткими ограничениями.

 


Вопросы для самопроверки

 

1. Опишите сущность механизма перегрузки стандартных операций.

2. Перечислите основные ограничения перегрузки операций.

3. Как можно определить функцию-операцию по отношению к классу, для которого выполняется перегрузка операции?

4. Как должен в общем виде выглядеть прототип функции-операции, перегружающей префиксную унарную операцию?

5. Почему операцию = невозможно перегрузить глобальной функцией? Обоснуйте ответ.

6. Прототип функции-операции, суммирующей две матрицы, имеет вид const Matrix & operator + (const Matrix & m2) const; Какая в нем имеется ошибка и каковы ее потенциальные последствия?

7. Прототип функции-операции присваивания, определенной для класса матриц, имеет вид Matrix & operator = (const Matrix & m) const; Какого рода ошибка содержится в приведенном прототипе?

8. Требуется реализовать операцию суммирования матриц класса CMatrix с вещественным выражением с сохранением свойства коммутативности операции. Как следует определить эту функцию-операцию? Дайте эскиз определения.

9. Как правильно построить заголовок функции-операции operator –, которая изменяет знак всех элементов матриц класса CMatrix на противоположный?

10. Почему функция operator = в общем случае должна проверять присваивание объекта самому себе? Когда такая проверка избыточна?

11. Опишите алгоритм работы функции operator =, если класс, для которого она перегружена, является производным от другого класса.

12. Пусть для класса CRationalNumber, представляющего рациональные числа, требуется определить функцию operator <<. Как это сделать наиболее грамотно?

13. Как правильно задать заголовок функции operator <<, чтобы были возможны операторы вида cout << m1 << m2 << m3, где m1,m2,m3 – объекты класса, для которого определяется функция operator<<?

14. Можно ли операцию [] перегрузить дружественной функцией? Поясните ответ.

15. Почему операция operator [] обычно перегружается дважды?

16. В классе Matrix, описывающем прямоугольные матрицы, имеется функция const Matrix operator + (double r) const; Она увеличивает элементы матрицы на выражение r. Можно ли этой функцией вычислить выражение 0.5+m? Обоснуйте ответ.

17. Программа содержит оператор m1 = m1 + m2, где m1 и m2 – объекты некоторого класса C, для которого перегружены операции = и +. Перепишите этот оператор в эквивалентной функциональной форме.

18. Чем отличаются заголовки функций-операций пре-инкремента (++c) и постинкремента (c++)?

19. Почему функция-операция префиксного декремента (--c) должна возвращать ссылку на класс? Обоснуйте ответ.

20. Заголовок операции суммирования, определенной для класса матриц, имеет вид: virtual const Matrix operator + (const Matrix & m2) const; Как правильно переопределить эту операцию в производном классе, например, поименованных матриц? Приведите только заголовок и обоснуйте выбранный вариант.

21. В базовом классе BasicMatrix, представляющем матрицы, определена операция вывода в поток с заголовком: friend ostream & operator << (ostream & stream, const BasicMatrix & m); От класса BasicMatrix порожден производный класс NamedMatrix, описывающий поименованные матрицы. Как наиболее рационально реализовать операцию записи в поток для этого производного класса?

22. Для класса NamedMatrix, являющегося производным от класса Matrix, определена операция чтения из потока: istream & operator >> (istream & stream, Matrix & m) { return stream >> dynamic_cast<Matrix &>(m); } Какого рода ошибка есть в этом определении? Каковы “симптомы” ее проявления?

23. В классе, представляющем двоичные векторы BoolVector, определена операция индексации с заголовками bool & operator [] (int i); (вариант 1) и const bool operator [] (int i) const; (вариант 2). Какой из вариантов операции будет использован в операторе cout<<v[i], если вектор v определен как неконстантный объект класса BoolVector? Обоснуйте ответ.

24. В классе, представляющем двоичные векторы BoolVector, определена операция с заголовком const BoolVector & operator | (const BoolVector & v) const; обеспечивающая поэлементное ИЛИ над векторами-операндами. Какого рода ошибка заключена в приведенном заголовке?

25. Запишите заголовок функции-операции явного преобразования объекта класса CClass2 в ранее определенный класс CClass1.

26. Чем вызвана невозможность корректной перегрузки операций && и ||? Почему операции & и | в отличие от первых двух можно легко перегрузить корректно? Дайте обоснование.

27. Приведите пример определения класса функциональных объектов (функторов).

28. Имеется прототип функции-операции: double operator () (double, double, double,...)? Покажите как можно вызвать эту функцию.

29. В классе функторов CRandomGenerator (генератор случайных чисел с равномерным распределением на отрезке [a,b]) определена лишь одна следующая операция: double operator () (double a, double b) { randomize(); return a + fabs(b - a) * random(10000) / 10000; } Как воспользоваться функциональностью этого класса в программе (получить случайное число)? Приведите фрагмент кода.

30. Каким образом можно перегрузить функцию operator new? Опишите общие принципы перегрузки.

31. В чем состоит отличие между операциями new и operator new, delete и operator delete?

32. Как можно установить специальный обработчик ошибок выделения памяти операцией new? Ответ поясните примером.

33. В программе есть оператор вида CNamedArray * parr = new (MyNewHandlerFunc) CNamedArray("array", array, sizeof(array)/sizeof(array[0])); Он создает в динамической памяти объект класса CNamedArray (поименованный массив). Что представляет из себя имя MyNewHandlerFunc и как должна выглядеть его декларация?

 

Задачи

 

Решить задачи 1, 2 главы 5, используя возможности механизма перегрузки операций[38]. Использовать по возможности как компонентные, так и глобальные функции-операции.

 

 


Механизм исключений

Исключения (исключительные ситуации) – это особые состояния вычислительного процесса, которые требуют специальной обработки. Они могут быть связаны с внутренними ошибками программы или с условиями (не обязательно ошибочными), которые искусственно формируются по указанию программиста. Отличие исключительных ситуаций от просто ошибок состоит в том, что в момент их возникновения нормальное выполнение программы прекращается и управление немедленно передается в ближайший по вложенности участок, который реализует их обработку, независимо от контекста, где возникло исключение. Если обработка исключения не выполняется, то происходит принудительное завершение программы. Механизм исключений в С++ достаточно тесно связан с объектным подходом и поэтому рассматривается как подраздел в рамках объектно-ориентированного программирования на С++.

7.1. Модель исключений в С++. Синтаксис
и семантика обработки исключений

Для реализации обработки исключений С++ содержит три служебных слова: try, catch, throw. Служебное слово try позволяет выделить в любом месте программы контролируемый участок (блок), т.е. блок, где потенциально возможно возникновение исключительных ситуаций. Синтаксис его применения следующий:

try { statements }

После возникновения исключения в контролируемом блоке управление немедленно передается за его пределы, где должны быть размещены обработчики исключений. Каждый обработчик идентифицируется служебным словом catch и записывается в следующем формате:

catch (declaration) { statements }

где declaration определяет тип исключений, обрабатываемых данным обработчиком; в фигурных скобках записаны операторы блока обработки исключения.

Пример

try { // контролируемый блок

DataObject.AllocateMemory();

InputStream >> DataObject;

DataObject.Process();

OutputStream << DataObject;

}

catch(const char * e) // обработчик исключений типа const char *

{ /*... */ }

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

try { statements }



Поделиться:


Последнее изменение этой страницы: 2016-08-14; просмотров: 198; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.137.215.202 (0.011 с.)