Figure 4.7 Class Hierarchy Showing Duplicate Base Classes

Figure 4.7 Class Hierarchy Showing Duplicate Base Classes - student2.ru

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;

}

Наши рекомендации