Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ООП / учебное_пособие

.pdf
Скачиваний:
117
Добавлен:
24.02.2016
Размер:
1.6 Mб
Скачать

~cls(){}

void *operator new(size_t,int); };

void *cls::operator new(size_t tp,int n) // локальная функция operator

{cout << "локальная функция " <<endl; return new char[tp*n]; }

int main()

{cls obj("перегрузка оператора new"); float *ptr1,*ptr2,*ptr3;

ptr1=new (5) float;

// вызов 1 глобальной функции

operator new

ptr2=new (2,3) float;

// вызов 2 глобальной функции

operator new

ptr3=new float;

// вызов сиcтемной глобальной функции

cls *ptr4=new (3) cls("aa");

// вызов локальной функции operator new,

return 0;

// используя cls::cls("aa")

}

Результаты работы программы: глобальная функция 1 глобальная функция 2 локальная функция

Первое обращение ptr1=new (5) float приводит к вызову глобальной функции operator с одним параметром, в результате выделяется память 5*sizeof(float) байт (это соответствует массиву из пяти элементов типа float) и адрес заносится в указатель ptr1. Второе обращение приводит к вызову функции operator с двумя параметрами. Следующая инструкция new float приводит к вызову системной функции new. Инструкция new (3) cls("aa") соответствует вызову функции operator, описанной в классе cls. В функцию в качестве имени типа передается тип созданного объекта класса cls. Таким образом, ptr4 получает адрес массива из трех объектов класса cls.

Оператор delete разрешается доопределять только по отношению к классу. В то же время можно заменить системную версию реализации оператора delete на свою.

Доопределенную функцию operator delete можно объявить:

void operator delete(void *p<,size_t t>); void operator delete[](void *p<,size_t t>);

Функция operator должна возвращать значение void и имеет один обязательный аргумент типа void * − указатель на область памяти, которая должна быть освобождена. Ниже приведен пример программы с доопределением опе-

ратора delete.

#include <iostream> using namespace std; #include "string.h"

121

#include "stdlib.h"

void *operator new(size_t tip,int kol) // глобальная функция operator

{ cout << "глобальная функция NEW" <<endl;

return new char[tip*kol]; // вызов системной функции new

}

class cls

{ char a[40]; public:

cls(char *aa)

{ cout<<"конструктор класса cls"<<endl; strcpy(a,aa);

}

~cls(){}

void *operator new(size_t,int); void operator delete(void *);

};

void *cls::operator new(size_t tip,int n) // локальная функция operator

{ cout << "локальная функция " <<endl;

return new char[tip*n]; // вызов системной функции new

}

void cls::operator delete(void *p)

// локальная функция operator

{ cout << "локальная функция DELETE" <<endl;

delete p;

// вызов системной функции delete

}

 

 

void operator delete[](void *p)

// глобальная функция operator

{ cout << "глобальная функция DELETE" <<endl;

delete p;

// вызов системной функции delete

}

 

 

int main()

{cls obj("перегрузка операторов NEW и DELETE"); float *ptr1;

ptr1=new (5) float; // вызов глобальной функции доопр. оператора new delete [] ptr1; // вызов глобальной функции доопр. оператора delete cls *ptr2=new (10) cls("aa"); // вызов локальной функции доопределения

 

// оператора new (из класса cls)

delete ptr2;

// вызов локальной функции доопределения

return 0;

// оператора delete (из класса cls)

}

Результаты работы программы: глобальная функция NEW глобальная функция DELETE

122

локальная функция NEW конструктор класса cls локальная функция DELETE

Инструкция cls *ptr2=new (10) cls("aa") выполняется следующим образом: вначале вызывается локальная функция operator для выделения памяти, равной 10*sizeof(cls), затем вызывается конструктор класса cls.

Необходимо отметить тот факт, что при реализации переопределения глобальной функции в ней не должен использоваться оператор delete [], так как это приведет к бесконечной рекурсии. При выполнении инструкции системный оператор delete ptr2 сначала вызывается локальная функция доопределения оператора delete для класса cls, а затем из нее глобальная функция переопреде-

ления delete.

Далее рассмотрим пример доопределения функций new и delete в одном из классов, содержащемся в некоторой иерархии классов.

#include <iostream> using namespace std;

class A

{ public:

A(){cout<<"конструктор A"<<endl;}

virtual ~A(){cout<<"деструктор A"<<endl;} //виртуальный деструктор void *operator new(size_t,int);

void operator delete(void *,size_t);

};

class B : public A

{ public:

B(){cout<<"конструктор В"<<endl;} ~B(){cout<<"деструктор В"<<endl;}

};

void *A::operator new(size_t tip,int n)

{ cout << "перегрузка operator NEW" <<endl; return new char[tip*n];

}

void A::operator delete(void *p,size_t t) //глобальная функция operator { cout << " перегрузка operator DELETE" <<endl;

delete p;

// вызов глобальной (системной) функции

}

 

int main()

{A *ptr1=new(2) A; // вызов локальной функции, используя A::A() delete ptr1;

A *ptr2=new(3) B; // вызов локальной функции, используя B::B() delete ptr2;

123

return 0;

}

Результаты работы программы:

перегрузка operator NEW

конструктор A

деструктор A

перегрузка operator DELETE перегрузка operator NEW

конструктор A конструктор В деструктор В деструктор A

перегрузка operator DELETE

При public-наследовании класса А классом В public-функции new и delete наследуются как public функции класса В. Отметим, что необходимость использования в данном примере виртуального деструктора рассмотрена ранее.

5.3. Преобразование типа

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

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

5.3.1. Явные преобразования типов

Явное приведение типа, выполненное в стиле языка С, имеет вид

(тип) выражение.

То есть если перед выражением указать имя типа в круглых скобках, то значение выражения будет преобразовано к данному типу. Например:

int a = (int) b;

char *s=(char *) addr;

Недостатком их является полное отсутствие контроля, что приводит к ошибкам и путанице. Существует большая разница между приведением указателя на const объект к указателю на не const объект (изменяется только атрибут объекта) и приведением указателя на объект базового класса к указателю на объект производного класса (изменяется полностью тип указателя). Было бы неплохо более точно определять цель каждого приведения. Чтобы преодолеть недостатки приведения типов, выполняемых в стиле языка С, в язык С++ вве-

дены четыре оператора приведения типа: static_cast, const_cast, reinterpret_cast

124

и dynamic_cast. Спецификация преобразования имеет следующий вид:

оператор_приведения _типа<тип> (выражение).

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

int i,j;

double d=static_cast<double>(i)*j;

Вто же время, например, преобразование int к int* (и обратно) приведет

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

int i;

void *adr= reinterpret_cast<void *> (i);

Необходимо отметить, что reinterpret_cast не выполнит преобразование таких типов как float, double, объектов к типу указателя.

Если же нужно выполнить преобразование выражения, имеющего тип с атрибутами const и volatile, к типу без них, и наоборот, то можно использовать оператор const_cast:

const char *s;

char *ss=const_cast<char*> (s);

5.3.2. Информация о типе во время выполнения

Для динамической идентификации типов во время выполнения (Run-time type information, RTTI) в программах, манипулирующих объектами через указатели или ссылки на базовые классы, можно получить истинный производный тип адресуемого объекта. Для поддержки RTTI в языке C++ есть два оператора:

-оператор dynamic_cast поддерживает преобразования типов во время выполнения, позволяет преобразовывать указатель (или ссылку) на базовый класс в указатель (или ссылку) на производный от него;

-оператор typeid (определён в файле typeinfo.h) позволяет получить фактический производный тип объекта, адресованного указателем или ссылкой.

Необходимо отметить, что для получения информации о типе производного класса операнд любого из операторов dynamic_cast или typeid должен иметь тип класса, в котором есть хотя бы одна виртуальная функция. Таким образом, операторы RTTI – это события виремени выполнения для классов с виртуальными функциями и события времени компиляции для всех остальных типов.

5.3.3. Оператор dynamic_cast

Оператор dynamic_cast предназначен для приведения указателя или ссылки на объект базового класса к указателям или ссылкам на объекты произ-

125

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

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

#include <iostream> #include <string.h> #include <locale.h> using namespace std;

class university

{ protected:

char un[20]; public:

university(char *u) {strcpy(un,u);}

virtual void show(); // класс полиморфный virtual ~university(){}

};

class department:public university

{ protected: char dp[30];

public:

department(char *u,char *d):university(u) {strcpy(dp,d);} void show();

virtual void f(){cout<<" ф-ция f() класса department";}

};

class gruppa:public department

{ int gr; public:

gruppa(char *u,char *d,int g):department(u,d) {gr=g;} void show();

void f(){cout<<" ф-ция f() класса gruppa";}

};

void university::show(){cout<<" "<<un;} void department::show(){cout<<" "<<dp;} void gruppa::show(){cout<<" "<<gr;}

void fun_s(university &ob) { try{

gruppa &s1 = dynamic_cast< gruppa& >(ob); 126

s1.show(); s1.f(); cout<<endl; return;

}

catch(bad_cast){} try{

department &s2 = dynamic_cast< department& >(ob); s2.show(); s2.f(); cout<<endl; return;

}

catch(bad_cast){} try{

university &s3 = dynamic_cast< university& >(ob); s3.show(); cout<<endl; return;

}

catch(bad_cast){}

}

void fun_u(university *uk)

{gruppa *p1 = dynamic_cast< gruppa* >(uk); department *p2 = dynamic_cast< department* >(uk); university *p3 = dynamic_cast< university* >(uk); if(!p1 && !p2 && !p3)

{ cout<<"ошибка преобразования типа"<<endl;

return;

 

}

 

if(p1){ p1->show();

p1->f(); cout<<endl; return;}

if(p2){ p2->show();

p2->f(); cout<<endl; return;}

if(p3){ p3->show(); cout<<endl; return;}

}

void main()

{setlocale (LC_ALL, "RUS"); university ob_un("BSUIR");

department ob_dp("BSUIR","факультет"); gruppa ob_gr("BSUIR","факультет",12345);

cout<<"функция fun_u преобразования указателей"<<endl; fun_u(&ob_un);

fun_u(&ob_dp); fun_u(&ob_gr);

cout<<" функция fun_s преобразования ссылок"<<endl; fun_s(ob_un);

fun_s(ob_dp);

fun_s(ob_gr);

}

Параметром метода fun_u является указатель на объект university который может адресовать один из типов university, department или gruppa. По-

127

скольку fun_u обращается к виртуальному методу show(), то вызывается подходящая замещающая функция, определенная в соответствующем классе, в зависимости от того, какой объект адресован указателем.

Допустим, класс gruppa перестал удовлетворять нашим потребностям, и мы хотим его модифицировать, добавив еще один метод f(), используемую совместно с show(). Для этого нужно его добавить начиная с класса university.

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

Это позволит в методе получающем указатель на базовый класс преобразовать его к указателю на производный класс, для обеспечения работы с теми его элементами, которые по-другому не доступны (т.е. через указательна базовый класс).

Таким образом, оператор dynamic_cast осуществляет сразу две операции. Он проверяет, выполнимо ли запрошенное приведение, и если это так, выполняет его. Проверка производится во время работы программы. dynamic_cast безопаснее, чем другие операции приведения типов в C++, поскольку проверяет возможность корректного преобразования.

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

Врассмотренном примере так-же имеется метом fun_s d rjnjhjv операция dynamic_cast применять для преобразования ссылки на объект типа базового класса в ссылку на тип производного. Поскольку нулевых ссылок не бывает, то проверить успешность выполнения операции путем сравнения результата (т.е. возвращенной оператором dynamic_cast ссылки) с нулем невозможно.

Для извещения об ошибке в случае приведения к ссылочному типу оператор dynamic_cast возбуждает исключение (исключения будут рассмотрены несколько позже). В случае неудачного завершения ссылочного варианта dynamic_cast возбуждается исключение типа bad_cast.

5.3.4. Оператор typeid

Оператор typeid, входящий в состав RTTI, позволяет выяснить фактический тип выражения. Если оно принадлежит типу класса и этот класс содержит

128

хотя бы один виртуальный метод, то ответ может и не совпадать с типом самого выражения. Так, если выражение является ссылкой (или указателем) на базовый (полиморфный, содержащий виртуальный метод) класс, то typeid сообщает тип объекта на который указывает ссылка (указатель). Если базовый класс не является полиморфным то typeid вернет тип базового класса, например:

class university

{ public:

virtual ~university(){}

};

class department:public university

{ };

void main()

{setlocale (LC_ALL, "RUS"); university *p;

p=new department();

cout<<"Указатель на "<<typeid(*p).name()<<endl;

}

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

Указатель на класс department

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

Указатель на класс university

Ниже приведен более полный пример программы, демонстрирующей использование оператора typeid.

#include <iostream> #include <string.h> #include <locale.h> using namespace std;

class university

{ protected:

char un[20]; public:

university(char *u) {strcpy(un,u);} virtual ~university(){}

};

class department:public university

{ protected: char dp[30];

public:

129

department(char *u,char *d):university(u) {strcpy(dp,d);}

};

class gruppa:public department

{ int gr; public:

gruppa(char *u,char *d,int g):department(u,d) {gr=g;}

};

void fun_s(university &ob)

{// применение оператора typeid к ссылке cout<<"Ссылка на "<<typeid(ob).name()<<endl;

}

void fun_u(university *uk)

{// применение typeid к указателю на полиморфный класс cout<<"Указатель на "<<typeid(*uk).name()<<endl;

}

void main()

{setlocale (LC_ALL, "RUS"); int i=1, j=2;

float f=1.2;

university ob_un("BSUIR");

department ob_dp("BSUIR","факультет"); gruppa ob_gr("BSUIR","факультет",12345);

//вывод втроеных и своего типа переменных cout<<"Тип переменной i - "<<typeid(i).name()<<endl;

cout<<"Тип переменной f - "<<typeid(f).name()<<endl; cout<<"Тип значения 12.34 - "<<typeid(12.34).name()<<endl; if(typeid(i)==typeid(j)) // сравнение типов переменных i и j

cout<<"Переменные i и j одного типа"<<endl; if(typeid(i)!=typeid(f))

cout<<"Переменные i и f разных типов"<<endl; cout<<"Тип переменной ob_un - "<<typeid(ob_un).name()<<endl; cout<<"Тип переменной ob_dp - "<<typeid(ob_dp).name()<<endl; cout<<"Тип переменной ob_gr - "<<typeid(ob_gr).name()<<endl;

cout<<"вывод типа указателя на объект в функции fun_u"<<endl; fun_u(&ob_un);

fun_u(&ob_dp); fun_u(&ob_gr);

cout<<"вывод типа ссылки на объект в функции fun_s"<<endl; fun_s(ob_un);

fun_s(ob_dp); fun_s(ob_gr);

}

130