Figure 4.7 Class Hierarchy Showing Duplicate Base Classes
Given an object of type E and a pointer to the D subobject, to navigate from the D subobject to the left-most A subobject, three conversions can be made. You can perform a dynamic_cast conversion from the D pointer to an E pointer, then a conversion (either dynamic_cast or an implicit conversion) from E to B, and finally an implicit conversion from B to A. For example:
void f(D* pd){ E* pe = dynamic_cast<E*>(pd); B* pb = pe; // upcast, implicit conversion A* pa = pb; // upcast, implicit conversion}The dynamic_cast operator can also be used to perform a “cross cast.” Using the same class hierarchy, it is possible to cast a pointer, for example, from the B subobject to the D subobject, as long as the complete object is of type E.
Considering cross casts, it is actually possible to do the conversion from a pointer to D to a pointer to the left-most A subobject in just two steps. You can perform a cross cast from D to B, then an implicit conversion from B to A. For example:
void f(D* pd){ B* pb = dynamic_cast<B*>(pd); // cross cast A* pa = pb; // upcast, implicit conversion}A null pointer value is converted to the null pointer value of the destination type by dynamic_cast.
When you use dynamic_cast < type-id > ( expression ), if expression cannot be safely converted to type type-id, the run-time check causes the cast to fail. For example:
class A { ... }; class B { ... }; void f(){ A* pa = new A; B* pb = dynamic_cast<B*>(pa); // fails, not safe; // B not derived from A ...}The value of a failed cast to pointer type is the null pointer. A failed cast to reference type throws a bad_cast exception.
Преобразования типов, определенных в программе
Конструктор с одним аргументом можно явно не вызывать.
#include "iostream.h"
class my_class
{ double x,y; //
public:
my_class(double X){x=X; y=x/3;}
double summa();
};
double my_class::summa() { return x+y; }
void main()
{ double d;
my_class my_obj1(15), // создание объекта obj1 и инициализация его
my_obj2=my_class(15), // создание объекта obj2 и инициализация его
my_obj3=15; // создание объекта obj3 и инициализация его
d=my_obj1; // error no operator defined which takes a right-hand operand of
// type 'class my_class' (or there is no acceptable conversion)
cout << my_obj1.summa() << endl;
cout << my_obj2.summa() << endl;
cout << my_obj3.summa() << endl;
}
В рассматриваемом примере все три создаваемых объекта будут инициализированы числом 15 (первые два явным, третий неявным вызовом конструктора). Следовательно, значение базовой переменной (определенной в языке) может быть присвоено переменной типа, определенного пользователем.
Для выполнения обратных преобразований, то есть от переменных, имеющих тип, определенный пользователем к базовому типу, можно задать преобразования с помощью соответствующей функции operator(), например:
class my_class
{ double x,y;
public:
operator double() {return x;}
my_class(double X){x=X; y=x/3;}
double summa();
};
Теперь в выражении d=my_obj1 не будет ошибки, так как мы задали прямое преобразование типа. При выполнении этой инструкции активизируется функция operator, преобразующая значение объекта к типу double и возвращающая значение компоненты объекта . Наряду с прямым преобразованием в С++ имеется подразумеваемое преобразование типа:
#include "iostream.h"
class my_cl1
{ double x; //
public:
operator double(){return x;}
my_cl1(double X) : x(X) {}
};
class my_cl2
{ double x; //
public:
operator double(){return x;}
my_cl2(my_cl1 XX): x(XX) {}
};
void fun(my_cl2 YY)
{ cout << YY <<endl; }
void main()
{ fun(12); // ERROR cannot convert parameter 1
// from 'const int' to 'class my_cl2'
fun(my_cl2(12)); // подразумеваемое преобразование типа
}
В этом случае для инструкции fun(my_cl2(12)) выполняется следующее:
активизируется конструктор класса my_cl1 (x инициализируется значением 12);
при выполнении в конструкторе my_cl2 инструкции x(XX) вызывается функция operator (класса my_cl1), возвращающая значение переменной x, преобразованное к типу double;
далее при выполнении инструкции cout << YY вызывается функция operator() класса my_cl2, выполняющая преобразование значения объекта YY к типу double.
Разрешается выполнять только одноуровневые подразумеваемые преобразования. В приведенном выше примере инструкция fun(12) соответствует двухуровневому неявному преобразованию, где первый уровень - my_cl1(12) и второй - my_cl2(my_cl1(12))
В заключение отметим основные правила доопределения операторов:
- все операторы языка С++ за исключением . * :: ?: sizeof и символов # ## можно доопределять;
- при вызове функции operator используется механизм перегрузки функций;
- количество операндов, которыми оперируют операторы (унарные, бинарные), и приоритет операций сохраняются и для доопределенных операторов.
Шаблоны
Параметризированные классы
Параметризированный класс – некоторый шаблон, на основе которого можно строить другие классы. Этот класс можно рассматривать как некоторое описание множества классов, отличающихся только типами их данных. В С++ используется ключевое слово template для обеспечения параметрического полиморфизма. Параметрический полиморфизм позволяет использовать один и тот же код относительно различных типов (параметров тела кода). Это наиболее полезно при определении контейнерных классов. Шаблоны определения класса и шаблоны определения функции позволяют многократно использовать код, корректно по отношению к различным типам, позволяя компилятору автоматизировать процесс реализации типа.
Шаблон класса определяет правила построения каждого отдельного класса из некоторого множества разрешенных классов.
Спецификация шаблона класса имеет вид:
template <список параметров>
Class объявление класса
Список параметров класса-шаблона представляет собой идентификатор типа, подставляемого в объявление данного класса при его генерации. Идентификатору типа предшествует ключевое слово class или typename. Рассмотрим пример шаблона класса работы с динамическим массивом и выполнением контроля за значениями индекса при обращении к его элементам.
#include "iostream.h"
#include "string.h"
template <class T> // или иначе template <typename T>
Class vector
{ T *ms;
int size;
public:
vector() : size(0),ms(NULL) {}
~vector(){delete [] ms;}
void inkrem(const T &t) // увеличение размера массива на 1 элемент
{ T *tmp = ms;
ms=new T[size+1]; // ms - указатель на новый массив
if(tmp) memcpy(ms,tmp,sizeof(T)*size); // перезапись tmp -> ms
ms[size++]=t; // добавление нового элемента
if(tmp) delete [] tmp; // удаление временного массива
}
void decrem(void) // уменьшение размера массива на 1 элемент
{ T *tmp = ms;
if(size>1) ms=new T[--size];
if(tmp)
{ memcpy(ms,tmp,sizeof(T)*size); // перезапись без посл.элемента
delete [] tmp; // удаление временного массива
}
}
T &operator[](int ind) // определение обычного метода
{ // if(ind<0 || (ind>=size)) throw IndexOutOfRange; // возбуждение
// исключительной ситуации IndexOutOfRange
return ms[ind];
}
};
void main()
{ vector <int> VectInt;
vector <double> VectDouble;
VectInt. inkrem(3);
VectInt. inkrem(26);
VectInt. inkrem(12); // получен int-вектор из 3 атрибутов
VectDouble. inkrem(1.2);
VectDouble. inkrem(.26);//получен double-вектор из 2 атрибутов
int a=VectInt[1]; // a = ms[1]
cout << a << endl;
int b=VectInt[4]; // будет возбуждена исключительная ситуация
cout << b << endl; // но не обработана
double d=VectDouble[0];
cout << d << endl;
VectInt[0]=1;
VectDouble[1]=2.41;
}
Класс vector наряду с конструктором и деструктором имеет 2 функции: increm – добавление в конец вектора нового элемента, dekrem – уменьшение числа элементов на единицу и операция [] обращения к i-му элементу вектора.
Параметр шаблона vector – любой тип, у которого определены операция присваивания и операция new. Например, при задании объекта типа vector <int> происходит генерация конкретного класса из шаблона и конструирование соответствующего объекта VectInt, при этом тип Т получает значение типа int. Генерация конкретного класса означает, что генерируются все его компоненты-функции, что может привести к существенному увеличению кода программы.
Выполнение функций
VectInt.increm(3);
VectInt.increm(26);
VectInt.increm(12);
приведет к созданию вектора (массива) из трех атрибутов (3, 26 и 12 ).
Сгенерировать конкретный класс из шаблона можно, явно записав:
template vector<int>;
При этом не будет создано никаких объектов типа vector<int>, но будет сгенерирован класс со всеми его компонентами.
В некоторых случаях желательно описания некоторых компонент-функций шаблона класса выполнить вне тела шаблона, например:
#include "iostream.h"
template <class T1,class T2>
T1 sm1(T1 aa,T2 bb) // описание шаблона глобальной
{ return (T1)(aa+bb); // функции суммирования значений
} // двух аргументов
template <class T1,class T2>
Class cls
{ T1 a;
T2 b;
public:
cls(T1 A,T2 B) : a(A),b(B) {}
~cls(){}
T1 sm1() // описание шаблона функции
{ return (T1)(a+b); // суммирования компонент объекта obj_
}
T1 sm2(T1,T2); // объявление шаблона функции
};
template <class T1,class T2>
T1 cls<T1,T2>::sm2(T1 aa,T2 bb) // описание шаблона функции
{ return (T1)(aa+bb); // суммирования внешних данных
}
void main()
{ cls <int,int> obj1(3,4);
cls <double,double> obj2(.3,.4);
cout<<"функция суммирования компонент объекта 1 = "
<<obj1.sm1()<<endl;
cout<<"функция суммирования внешних данных (int,int) = "
<<obj1.sm2(4,6)<<endl;
cout<<"вызов глобальной функции суммирования (int,int) = "
<<sm1(4,.6)<<endl;
cout<<"функция суммирования компонент объекта 2 = "
<<obj2.sm1()<<endl;
cout<<"функция суммирования внешних данных (double,double)= "
<<obj2.sm2(4.2,.1)<<endl;
}