Skip to content

ООП Лекция 11. Приведение типов. Пространство имен.

Vladislav Mansurov edited this page May 2, 2022 · 1 revision

Приведение типов

image

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

Приведение типов в Си

В языке Си приведение типов: (<абстр. описатель>)<выражение>.

Когда создавался язык Си, все говорили, что это великолепно. Когда мы указываем такое приведение типов, компилятор, не задумываясь об их совместимости, осуществляет такое преобразование. Что мы получим в результате - неизвестно. Для каких-то задач, например, системного применения, это можно использовать, это может быть даже удобно (разложить структуру по байтам и привести ее к массиву байтов). В общем случае, такое приведение не безопасно. Компилятор не предупреждает нас, возможно это или не возможно.

Пример (как правило, это не работает):

float A[10][10];
float** P = (float**) A; // это чистый бандитизм
// Каждый компилятор осуществит преобразование по-своему
// P - указатель на указатель на float, а A - адрес первой строки последовательности элементов.
// двойное разименование для P = неопределённость.

Приведение типов в Си++

В языке C++ решили использовать разные варианты приведения типов.

По умолчанию всегда указатель на производный класс приводится к указателю на базовый класс (или ссылка). А если нужно обратное преобразование?

Для обратного преобразования появились два оператора преобразования:

  • Первый оператор преобразования выполняется на этапе компиляции - static_cast
  • Второй оператор преобразования на этапе выполнения - dynamic_cast.

Оператор static_cast

На этапе компиляции выполняется оператор static_cast. Этот оператор используется для приведения родственных классов, находящихся по одной ветви наследования. Также используется для стандартных типов для которых определён механизм явного приведения.

Рассмотрим следующую иерархию:

С помощью static_cast мы можем от указателя A привестись к указателю B или к указателю на класс C, или от A к D. Он не позволит нам привестись от класса указателя на B к D. Они родственные, но находятся по разным ветвям.

Пример:

A *pa = new B;               // У нас есть указатель
B *pb = static_cast<B*>(pa); // Приведение

Проблема - это выполняется на этапе компиляции. На этапе компиляции невозможно проверить, что это за объект, то есть приведение будет срабатывать, но реально указатель pa может не указывать на объект класса B.

Мы можем написать вот такую строчку:

A *pa = new B;               // У нас есть указатель
C *pc = static_cast<C*>(pa); // Приведение

Что будет в таком случае - известно одному Богу.

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

Оператор dynamic_cast

Оператор dynamic_cast делает проверку на этапе выполнения. Он приводит к типу, если реально указатель указывает на объект этого типа. Если нет - возвращает указатель на NULL. Приведение может быть не только указателем, но и ссылкой. Для dynamic_cast есть жесткое требование - базовый классы должен быть полиморфными (то есть либо virtual метод, либо virtual деструктор).

Пример:

pb = dynamic_cast<B*>(pa);
if (pb)

Можно проверить: если pb не равно нулю, приведение осуществилось, иначе не осуществилось. В данном случае всё будет нормально, так как pa указывает на объект класса B - приведение возможно. А приведение, например, к указателю на класс С невозможно из pa, так как pa указывает на объект другого класса.

Такое приведение типов удобно тем, что это удобно на этапе выполнение программ.

Если работаем со ссылкой в dynamic_cast - вместо NULL возникает исключение bad_cast, которое можно отловить. Исключения ловить неприятно, поэтому лучше работать с указателями.

Модификация константных объектов

Мы работаем с модификатором const. Мы контролируем, что объект может быть константными, контролируем методы. Но есть проблема - мы не можем менять поля константных объектов. Иногда возникают ситуации, когда нам необходимо менять...

Предположим, у нас есть объект, который держит указатель на другой. Мы определили его, как константный, но этот указатель мы хотим отобрать от него. Чтобы отобрать, нам нужно это поле обнулить. А сделать это мы не можем, так как не можем обнулять поля константных объектов.

Чтобы убрать модификатор const, используется оператор const_cast. Есть компиляторы, которые не позволяют изменять константность объектов. В MSVC такая возможность имеется.

Пример модификации константных объектов

class A
{
	int a = 0;
public:
	virtual ~A() = 0;
	void f() { cout << "method f class A:"<< a << endl; }
};

A::~A() {}

class B : public A
{
	int b = 1;
public:
	void f() { cout << "method f class B;" << b << endl; }
	void g1() { cout << "method g1 class B;" << endl; }
};

class C : public B
{
	int c = 2;
public:
	void f() { cout << "method f class C;" << c << endl; }
	void g2() { cout << "method g2 class B;" << endl; }
};

class D : public A
{
	int d = 3;
public:
	void f() { cout << "method f class D;" << d << endl; }
};

int main()
{
	A* pa = new B;
	B* pb = static_cast<B*>(pa);; // Всё компилируется, но есть проблема. Так как выполняется на этапе компиляции, приведение будет срабатывать, но реально pb может не указывать на обьект класса B.
	pb->f();
	C* pc = static_cast<C*>(pa); // Всё компилируется. Указатель на обьект класса C, но реально - на обьект класса B. Что будет, НЕИЗВЕСТНО.
	pc->f();
	D* pd = static_cast<D*>(pa);
	pd->f();
	pb = dynamic_cast<B*>(pa);
	if (!pb)
	{
		cout << "Error bad cast!" << endl;
	}
	else
	{
		pb->f();
		pb->g1();
	}
	pc = dynamic_cast<C*>(pa); // НЕВОЗМОЖНО, т. к. pa -> обьект класса B
	if (!pc)
	{
		cout << "Error bad cast!" << endl;
	}
	else
	{
		pc->f();
		pc->g2();
	}
	const B obj;
	const B* p = &obj;
	const_cast<B*>(p)->f();// У класса B метод f() не константный, то есть мы можем вызвать этот метод,
                           // если объект не константный
}

Оператор reinterpret_cast

Также существует оператор, эквивалентный С-му приведению - reinterpret_cast. Может приводить из любого типа.

Пример:

class A {...};
A* p = new A;
char* pbyte = reinterpret_cast<char*>(p); // Можем выполнить такой бандитизм

Мы поставили указатель типа char на первый байт объекта класса А.

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

Пространство имён

Программа растет, она становится большой. Во время разрастания программы может возникнуть проблема конфликта имён. Мы используем разные библиотеки, в одной библиотеке нужно что-то взять, в другой что-то взять... у некоторых библиотек может дублироваться функционал. Это типичная ситуация. Например, несколько реализаций функции с именем swap в различных библиотеках. Когда подключаем разные библиотеки, может возникнуть конфликт имен. Из какой библиотеки мы вызываемswap? Это так же может касаться имен классов, методов, которые мы используем.

Эту проблему пытались решить раньше. Например, в Fortran были так называемые "общие блоки памяти", когда мы использовали идентификатор, и помечали, из какого блока памяти мы его используем, чтобы решить этот конфликт.

В C++ мы можем задавать пространства имён.

Синтаксис такой:

namespace <имя>
{
    <блок пространства имён>
}

// Доступ к пространству имён
<имя>::f(); // f() - член пространства имен

Или можно сделать так:

// или можно включить это пространство и использовать f
using namespace <имя>;
f(); // но таким образом можно вернуться к изначальной проблеме

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

Пространствами имён злоупотреблять не надо. Вложенных пространств имён надо избегать или сводить к минимуму.

Имя пространства имён может отсутствовать - это анонимные пространства имён. Их особенность в том, что из другого файла нельзя получить доступ к членам анонимного пространства имён. Грубо говоря, если у нас часть, и мы не хотим, чтобы она была видна из других частей, мы можем определить это, как анонимное пространство имен.

Clone this wiki locally