
- •Объектно-ориентированное программирование:
- •Содержание
- •Новый тип данных – класс
- •1.2. Доступность компонентов класса
- •1.3. Конструктор и деструктор
- •1.4. Компоненты-данные и компоненты-функции
- •1.4.1. Данные – члены класса
- •1.4.2. Функции – члены класса
- •1.4.3. Константные компоненты-функции
- •1.4.4. Статические члены класса
- •Указатели на компоненты класса
- •1.5.1. Указатели на компоненты-данные
- •1.5.2.Указатели на компоненты-функции
- •1.6. Указатель this
- •1.7. Друзья классов
- •7.1. Дружественная функция
- •1.7.2. Дружественный класс
- •1.8. Определение классов и методов классов
- •2. Наследование
- •2.1. Определение производного класса
- •2.2. Конструкторы и деструкторы производных классов
- •2.3. Виртуальные функции
- •2.4. Абстрактные классы
- •2.5. Включение объектов
- •2.6. Включение и наследование
- •2.7. Множественное наследование
- •2.8. Локальные и вложенные классы
- •2.9. Пример программы для Microsoft Visual Studio
- •2.10. Упражнения
- •3. Перегрузка операций
- •3.1. Перегрузка унарных операций
- •3.2. Перегрузка бинарных операций
- •3.4. Перегрузка операции вызова функции
- •3.5. Перегрузка операции присваивания
- •3.6. Перегрузка операции new
- •3.7. Перегрузка операции delete.
- •3.8. Основные правила перегрузки операций
- •3.9. Примеры программ
- •4. Шаблоны функций и классов
- •4.1. Шаблоны функций
- •4.2. Шаблоны классов
- •4.3. Компонентные функции параметризованных классов
- •4.4. Примеры программ.
- •5. Обработка исключительных ситуаций
- •5.2. Получение дополнительной информации об исключении
- •5.3. Определение типа исключения
- •5.4. Иерархия исключений
- •5.5. Спецификация функций, обрабатывающих исключения
- •6. Потоковые классы
- •6.1. Библиотека потоковых классов
- •6.3. Стандартные потоки ввода-вывода
- •6.4. Форматирование
- •6.5. Манипуляторы
- •6.6. Ввод-вывод объектов пользовательских классов
- •6.7. Определение пользовательских манипуляторов
- •6.8. Пользовательские манипуляторы с параметрами
- •6.9. Использование макросов для создания манипуляторов
- •6.10. Состояние потока
- •6.11. Неформатированный ввод-вывод
- •6.12. Файловый ввод-вывод
- •7.1. Пространство имен
- •7.2. Динамическая идентификация типов
- •7.3. Безопасное приведение типа
- •8. Стандартная библиотека шаблонов
- •8.1. Введение в stl
- •Парадигму обобщенного программирования можно сформулировать следующим образом:
- •8.2. Итераторы
- •8.3. Классы-контейнеры
- •Iterator find(const key_type&k)
- •Iterator lower_bound(const key_type&k)
- •Iterator upper_bound(const key_type&k)
- •8.4. Контейнер vector
- •8.5. Многомерные массивы
- •8.6. Ассоциативные контейнеры
- •8.7. Объекты-функции
- •8.8. Алгоритмы
- •I. Немодифицирующие операции
- •II. Модифицирующие операции
- •III. Сортировка
- •IV. Работа с множествами
- •V. Минимумы и максимумы
- •VI. Перестановки
3.6. Перегрузка операции new
Операция new, заданная по умолчанию, может быть в двух формах:
new тип <инициализирующее выражение>
new тип[];
Первая форма используется не для массивов, вторая–для массивов.
Перегруженную операцию new можно определить в следующих формах, соответственно для не массивов и для массивов:
void* operator new(size_t t[,остальные аргументы] );
void* operator new[](size_t t[,остальные аргументы] );
Первый и единственный обязательный аргумент t всегда должен иметь тип size_t. Если аргумент имеет тип size_t, то в операцию-функцию new автоматически подставляется аргумент sizeof(t), т.е. она получает значение, равное размеру объекта t в байтах.
Например, пусть задана следующая функция:
void* operator new(size_t t,int n){return new char[t*n];}
и она вызывается следующим образом:
double *d=new(5)double;
Здесь t=double, n=5.
В результате после вызова значение t в теле функции будет равно sizeof(double).
При перегрузке операции new появляется несколько глобальных операций new, одна из которых определена в самом языке по умолчанию, а другие являются перегруженными. Возникает вопрос: как различить такие операции? Это делается путем изменения числа и типов их аргументов. При изменении только типов аргументов может возникнуть неоднозначность, являющаяся следствием возможных преобразований этих типов друг к другу. При возникновении такой неоднозначности следует при обращении к new задать тип явно, например:
new((double)5)double;
Одна из причин, по которой перегружается операция new, состоит в стремлении придать ей дополнительную семантику, например, обеспечение диагностической информацией или устойчивости к сбоям. Кроме того, класс может обеспечить более эффективную схему распределения памяти, чем та, которую предоставляет система.
В соответствии со стандартом С++ в заголовочном файле <new> определены следующие функции-операции new, позволяющие передавать наряду с обязательным первым size_t аргументом и другие.
void* operatop new(size_t t)throw(bad_alloc);
void* operatop new(size_t t,void* p)throw();
void* operatop new(size_t t,const nothrow&)throw();
void* operatop new(size_t t,allocator& a);
void* operatop new[](size_t t)throw(bad_alloc);
void* operatop new[](size_t t,void* p)throw();
void* operatop new[](size_t t,const nothrow&)throw();
Эти функции используют генерацию исключений(throw) и собственный распределитель памяти(allocator).
Версия с nothrow выделяет память как обычно, но если выделение заканчивается неудачей, возвращается 0, а не генерируется bad_alloc. Это позволяет нам для выделения памяти использовать стратегию обработки ошибок до генерации исключения.
Правила использования операции new
Объекты, организованные с помощью new, имеют неограниченное время жизни. Поэтому область памяти должна освобождаться оператором delete.
Если резервируется память для массива, то операция new возвращает указатель на первый элемент массива.
При резервировании памяти для массива все размерности должны быть выражены положительными величинами.
Массивы нельзя инициализировать.
Объекты классов могут организовываться с помощью операции new, если класс имеет конструктор по умолчанию.
Ссылки не могут организовываться с помощью операции new, так как для них не выделяется память.
Операция new самостоятельно вычисляет потребность в памяти для организуемого типа данных, поэтому первый параметр операции всегда имеет тип size_t.
Обработка ошибок операции new
Обработка ошибок операции new происходит в два этапа:
Устанавливается, какие предусмотрены функции для обработки ошибок. Собственные функции должны иметь тип new_handler и создаются с помощью функции set_new_handler. В файле new.h объявлены
typedef void(*new_handler)();
new_handler set_new_handler(new_handler new_p);
Вызывается соответствующая new_handler функция. Эта функция должна:
-либо вызвать bad_alloc исключение;
-либо закончить программу;
-либо освободить память и попытаться распределить ее заново.
Диагностический класс bad_alloc объявлен в new.h.
В реализации ВС++ включена специальная глобальная переменная _new_handler, значением которой является указатель на new_handler функцию, которая выполняется при неудачном завершении new. По умолчанию, если операция new не может выделить требуемое количество памяти, формируется исключение bad_alloc. Изначально это исключение называлось xalloc и определялось в файле except.h. Исключение xalloc продолжает использоваться во многих компиляторах. Тем не менее оно вытесняется определенным в стандарте С++ именем bad_alloc.
Рассмотрим несколько примеров.
Пример 1. В примере использование блока try...catch дает возможность проконтролировать неудачную попытку выделения памяти.
#include <iostream>
#include <new>
void main()
{double *p;
try{
p=new double[1000];
cout<<"Память выделилась успешно"<<endl;
}
catch(bad_alloc xa)
{cout<<"Ошибка выделения памяти\n";
cout<<xa.what(); return;} }
Пример 2. Поскольку в предыдущем примере при работе в нормальных условиях ошибка выделения памяти маловероятна, в этом примере ошибка выделения памяти достигается принудительно. Процесс выделения памяти длится до тех пор, пока не произойдет ошибка.
#include <iostream>
#include <new>
void main()
{double *p;
do{
try{
p=new double[1000];
//cout<<"Память выделилась успешно"<<endl;
}
catch(bad_alloc xa)
{cout<<"Ошибка выделения памяти\n";
cout<<xa.what();
return;}
}while(p);
}
Пример 3. Демонстрируется перегруженная форма операции new-операция new(nothow).
#include <iostream>
#include <new>
void main()
{double *p;
struct nothrow noth_ob;
do{
p=new(noth_ob) double[1000];
if(!p) cout <<"Ошибка выделения памяти\n";
else cout<<"Память выделилась успешно\n";
}while(p);
}
Пример 4. Демонстрируются различные формы перегрузки операции new.
#include<iostream.h>
#include<new.h>
double *p,*q,**pp;
class demo
{ int value;
public:
demo(){value=0;}
demo(int i){value = 1;}
void* operator new(size_t t,int,int);
void* operator new(size_t t,int);
void* operator new(size_t t,char*);
};
void* demo :: operator new(size_t t,int i, int j)
{
if(j) return new(i)demo;
else return NULL;
}
void* demo :: operator new(size_t t,int i)
{demo* p= ::new demo;
(*p).value=i;
return p;
}
void* demo::operator new(size_t t,char* z)
{
return ::new(z)demo;
}
void main()
{ class demo *p_ob1,*p_ob2;
// struct nothrow noth_ob;
p=new double;
pp=new double*;
p=new double(1.2); //инициализация
q=new double[3]; //массив
p_ob1=new demo[10]; //массив объектов demo
void(**f_ptr)(int); //указатель на указатель на функцию
f_ptr=new(void(*[3])(int)); //массив указателей на функцию
char z[sizeof(demo)]; //резервируется память в соответствии с величиной //demo
p_ob2=new(z)demo; //организуется demo-объект в области памяти на //которую указывает переменная z
p_ob2=new(3)demo; //demo-объект с инициализацией
p_ob1=new(3,0)demo; //возвращает указатель NULL
// p_ob2=new(noth_ob)demo[5];//массив demo-объектов,
// в случае ошибки возвращает NULL
}