Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
OOP_17.docx
Скачиваний:
1
Добавлен:
01.04.2025
Размер:
207.59 Кб
Скачать

Перегрузка бинарных операций

Перегрузка операций с двумя аргументами очень похожа на перегрузку бинарных операций:

Counter operator+ (Counter t)

{

Counter summ;

summ.counter = counter + t.counter;

return summ;

}

Counter c1,c2,c3;

c1.counter = 3;

c2.counter = 2;

c3 = c1 + c2;

Какая переменная вызовет функцию operator+? В перегруженных бинарных операциях всегда вызывается метод левого операнда. В данном случае метод operator+ вызывает объект c1.

В метод мы передаём аргумент. Аргументом всегда является правый операнд.

Кроме того, в данном случае операция + должна вернуть какой-то результат, чтобы присвоить его объекту c3. Мы возвращаем объект Counter. Возвращаемое значение присваивается переменной c3.

Заметьте, мы перегрузили операцию +, но не перегружали операцию =! Конечно же нужно добавить этот метод к классу Counter:

Counter operator= (Counter t)

{

Counter assign;

counter = t.counter;

assign.counter = t.counter;

return assign;

}

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

Counter c1(5),c2,c3;

c3 = c2 = c1;

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

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

С-style cast

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

Например, преобразование Double --> int:

int i = (int) 2.5;

Однако, такая форма приведения не наглядна, что существенно затрудняет поиск ошибок

Кроме того могут возникать ошибки при приведении пользовательских типов:

A * a = ...;

B * b = (B *) a;

Для некоторых типов могут возникать неопределенности. A и B могут быть никак не связаны, т.е. эта операция может не быть правомощной.

Надо различать преобразования связанных типов и несвязанных типов.

В С++ ввели свои именованные операторы преобразования типов:

 const_cast < > ( )

 static_cast < > ( )

 reinterpret_cast < > ( )

 dynamic_cast < > ( )

Теперь боле подробно о каждом операторе приведения типов

const_cast < > ( )

Оператор const_cast, как и следует из его имени позволяет убрать или добавить константность

A const * ac = ...;

A * a = const_cast<A *>(ac);

Пример

В массиве Array реализуем функцию get, в которой может быть много проверок.

И хотим написать вторую функцию через первую

T const & get (int i) const;

T & get (int i)

{

return const_cast<T &>(const_cast<Array const *>(this)->get(i));

}

Нельзя написать get(i), т.к. уйдем в рекурсию

Надо привести this к const Array, а потом убрать константность

Const_cast не применим:

Для тех объектов, которые объявлены как const, нельзы отменять константность. Будет undefined behaviour.

Если в функцию, например, передана константная ссылка, то внутри мы можем снять константность, хотя это не хорошо.

Для приведения констант применяется лишь оператор const_cast. Применение любого другого оператора приведения типов в данном случае привело бы к ошибке при компиляции. Аналогично, ошибка при компиляции произойдет в случае использования оператора const_cast в записи, которая осуществляет любые другие преобразования типов, отличные от создания или удаления константности

reinterpret_cast < > ( )

Приводит друг к другу указатели, которые друг от друга не зависят, не меняя константности

Оператор reinterpret_cast является жестко машинно-зависимым. Чтобы безопасно ипользовать операторreinterpret_cast, следует хорошо понимать, как именно реализованы используемые типы, а также то, как компилятор осуществляет приведение

Рассмотрим следующий пример приведения

int * p = ...;

double * d = reinterpret_cast<int *>(p);

Эта инструкция ни во что не компилируется, как и const_cast. Здесь не происходит вызова никакой функции. Это просто указание систее типов, что теперь у нас другой тип

В данном случае следует помнить, что фактическм типов объекта, адрес которого содержит указатель d, является int, а не символьный массив. Любая попытка применения указателя d там, где необходим обычный символьный указатель, вероятнее всего, потерпит неудачу именно во время выполнения. Например, его использование для инициализации объекта типа string приведет к ошибке во время выполнения. Это хороший пример, который служит для демонстрации того, почему явные приведения отнюдь небезопасны. Проблема заключается в том, что при изменении типа компилятор не выдаст никаких предупреждений или сообщений об ошибке. Компилятор не способен выяснить, адрес какого именно значения фактически хранит указатель. Отследить такие ошибки иногда чрезвычайно трудно, особенно если приведение указтеля к другому типу происходит в одном файле, а используется указатель - в другом.

static_cast < > ( )

Преобразование между связанными значениями.

Связанность проверяется на этапе компиляции, поэтому и называется static.

Типы, к котрым применим static_cast

 числовые

 типы, связанные наследованием

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

 к void *

Теперь подробнее о каждом преобразовании

 числовые типы

static_cast<int>(5.5);

 типы, связанные наследованием, а также ссылки и указатели на них

(Подразумеваются ссылки и указатели)

B * b = ...;

A * a = static_cast<A *>(b);

b = static_cast<B *>(a);

В том месте, где пишем это преобразование, должно быть известно, что B - наследник A

Если просто объявить эти классы:

struct A;

struct B;

то этот код не сработает

(А reinterpret_cast, как и const_cast, сработал бы, ему не надо знать, как устроены типы)

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

static_cast<string>("Hello");

Это эквивалентно:

string("Hello"); // Вызов конструктора

Что эквивалентно C-style cast:

(string)"Hello";

Основное место, где возникают проблемы, это шаблоны. Пусть заданы типы V и T. Считаем, что V наследуется от T. Если это не так, то код со static_cast не скомпилируется.

 void *

Можно преобразовать любой указатель к void * и обратно

Пример

Только объявим классы:

struct A;

struct B;

struct D;

A * a = ...;

B * b = static_cast<B *>(a); // Не скомпилируется

B * b = (B *)a; // Это сработает как reinterpret_cast

// Мы не сдвинем указатель, но компилятор не выдаст ошибки

Если же помимо объявления будут определения классов, то static_cast сработает правильно

Замечание: если приводим встроенные типы, то можно использовать C-style cast, в остальных случаях надо использовать C++ операторы преобразования типов

RTTI

RTTI = Run-Time Type Information

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

Большинство случаев использования RTTI можно избежать, от этого архитектура станет только лучше

RTTI обеспечивает два следующих оператора:

 Оператор typeid, которые возвращает фактический тип объекта, к которому относится указатель или ссылка.

 Оператор dynamic_cast, который осуществляет безопасное преобразование указатели или ссылки на базовый класс в указатель или сссылку на произвольный класс

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

typeid

Оператор typeid имеет следующий формат:

typeid(e)

Здесь е - это любое выражение или имя класса

Результатом операнда typeid является ссылка на объект библиотечного типа type_info. Чтобы использовать классtype_info, необходимо подключить библиотечный заголовок typeinfo:

#include <typeinfo>

type_info - структура, которая позволяет получить некоторую информацию о типе

Действия, которые можно производить с объектами типа type_info:

 Сравнение на равенство ==

 Сравнение на неравенство !=

 name() - возвращает символьную строку в стиле С, содержащую отображаемую версию имени типа. Может вернуть пустую строку, что не очень хорошо.

 before() - возвращает логическое значение, указывающее на то, следует ли один тип прежде другого. Порядок следования зависит от компилятора. Т.е. с помощью этого метода типы можно упорядочить.

A * a = new C();

type_info ti = typeid(* a); // Передается ссылка на объект

ti.name(); // "C"

typeid(int) - определяется в момент компиляции

typeid(A) - определяется в момент компиляции // "A"

typeid(a) - определяется в момент компиляции // "A * "

typeid(* a) - действие времени выполнения. Для того чтобы это действие действительно было времением выполения класс А должен обладать полиформизмом (должно быть наличие виртуальной функции), иначе вернет "A"

Пример

Хотим написать функцию пересечения двух фигур

bool intersert (shape * a, shape * b)

{

if (typeid(*a)== typeid(Circle))

{

if (...)

}

}

Т.е. перебор всех возможных вариантов - это n^2

Чтобы избавится от if можно написать map

map<pair<type_info, type_info>, bool(*)(shape *, shape *)>

Пусть есть M типов

Умеем переводить только некоторые из них:

 A --> B

 B --> C

 A --> D

 C --> E

 E --> D

Например, это преобразования единиц длины

Хотим перевести A --> E, но прямого такого преобразования нет, можно построить кратчайший путь, используяtype_info

dynamic_cast < > ( )

Можно воспользоваться оператором приведения типов dynamic_cast

if (Circle * c = dynamic_cast<Circle>(a))

{

... // Первая фигура - круг

}

Преобразование указателей с помощью dynamic_cast

A * a = new C();

B * b = dynamic_cast<B *>(a); ; // Вернет 0

Преобразование ссылок с помощью dynamic_cast

B & b = dynamic_cast<B &>(* a); ; // В случае неудачи бросит исключение

Т.е это корректный механизм, который проверяет правомерность преобразований

Но обычно всё можно реализовать без dynamic_cast