Вам бонус- начислено 1 монета за дневную активность. Сейчас у вас 1 монета

7. Пространство имен и динамическая идентификация типов

Лекция



Привет, сегодня поговорим про пространство имен, обещаю рассказать все что знаю. Для того чтобы лучше понимать что такое пространство имен, динамическая идентификация типов, динамическая идентификация типа данных, run-time type information, run-time type identification, rtti , настоятельно рекомендую прочитать все из категории С++ (C plus plus).

пространство имен (англ. namespace) — некоторое множество, под которым подразумевается модель, абстрактное хранилище или окружение, созданное для логической группировки уникальных идентификаторов (то есть имен).

Идентификатор, определенный в пространстве имен, ассоциируется с этим пространством. Один и тот же идентификатор может быть независимо определен в нескольких пространствах. Таким образом, значение, связанное с идентификатором, определенным в одном пространстве имен, может иметь (или не иметь) такое же значение, как и такой же идентификатор, определенный в другом пространстве. Языки с поддержкой пространств имен определяют правила, указывающие, к какому пространству имен принадлежит идентификатор (то есть его определение).

Например, Катя работает в компании X, а ID (сокр. от англ. Identifier — идентификатор) его как работника равен 123. Олег работает в компании Y, а его ID также равен 123. Единственное (с точки зрения некой системы учета), благодаря чему Катя и Олег могут быть различимы при совпадающих ID, это их принадлежность к разным компаниям. Различие компаний в этом случае представляет собой систему различных пространств имен (одна компания — одно пространство). Наличие двух работников в компании с одинаковыми ID представляет большие проблемы при их использовании, например, по платежному чеку, в котором будет указан работник с ID 123, будет весьма затруднительно определить работника, которому этот чек предназначается.

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

Операционные системы, многие современные языки программирования обеспечивают поддержку своей модели пространств имен: используют каталоги (или папки) как модель пространства имен. Это позволяет существовать двум файлам с одинаковыми именами (пока они находятся в разных каталогах). В некоторых языках программирования (например, C++, Python) идентификаторы имен пространств сами ассоциированы с соответствующими пространствами. Поэтому в этих языках пространства имен могут вкладываться друг в друга, формируя дерево пространств имен. Корень такого дерева называется глобальным пространством имен.

7. Пространство имен и динамическая идентификация типов

Эмуляция пространств имен

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

png_create_write_struct

png_get_signature

png_read_row

png_set_invalid

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

К недостаткам эмуляции пространств имен можно отнести

  • Отсутствие нормального учета вложенных пространств; идентификаторы становятся чересчур длинными.
  • Программисты или организации могут использовать резко несовместимые соглашения о наименовании, тем самым потенциально провоцируя большую запутанность.
  • Сложные операции или операции запроса над группами идентификаторов, основанных на пространствах имен, в которых они объявлены, обрабатываются слишком неоптимально или вообще невыполнимы.
  • Все вызовы идентификаторов должны на самом деле осуществлять с полным именем пространств .. Языки с непосредственной поддержкой пространств имен обычно предоставляют программисту возможность предварительно объявлять, что они хотят использовать некоторые (а то и все) идентификаторы в программе только из одного пространства, которые они впоследствии могут использовать без указания принадлежности к пространству.

Конфликт имен в программировании

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

Рассмотрим пример такого конфликта. boo.h и doo.h — это заголовочные файлы с функциями, которые выполняют разные вещи, но имеют одинаковые имена и параметры.

boo.h:

doo.h:

7. Пространство имен и динамическая идентификация типов

main.cpp:

Если boo.h и doo.h скомпилировать отдельно, то все пройдет без инцидентов. Однако, соединив их в одной программе, мы подключим две разные функции, но с одинаковыми именами и параметрами, в одну область видимости (глобальную), а это, в свою очередь, приведет к конфликту имен. В результате, компилятор выдаст ошибку. Для решения подобных проблем и добавили в язык С++ такую концепцию, как пространства имен.

Что такое пространство имен?

Пространство имен определяет область кода, в которой гарантируется уникальность всех идентификаторов. По умолчанию, глобальные переменные и обычные функции определены в глобальном пространстве имен. Например:

Глобальная переменная g_z и функция boo() определены в глобальном пространстве имен.

В примере, приведенном выше, при подключении файлов boo.h и doo.h обе версии doOperation() были включены в глобальное пространство имен, из-за чего, собственно, и произошел конфликт имен.

Чтобы избежать подобных ситуаций, когда два независимых объекта имеют идентификаторы, которые могут конфликтовать друг с другом при совместном использовании, язык C++ позволяет объявлять собственные пространства имен через ключевое слово namespace. Все, что объявлено внутри пользовательского пространства имен, — принадлежит только этому пространству имен (а не глобальному).

Перепишем заголовочные файлы из вышеприведенного примера, но уже с использованием namespace:

boo.h:

doo.h:

Теперь doOperation() из файла boo.h находится в пространстве имен Boo, а doOperation() из файла doo.h — в пространстве имен Doo. Об этом говорит сайт https://intellect.icu . Посмотрим, что произойдет при перекомпиляции main.cpp:

7. Пространство имен и динамическая идентификация типов

Результатом будет еще одна ошибка:

C:\Projects\Test.cpp(15) : error C2065: 'doOperation' : undeclared identifier

Случилось так, что когда мы попытались вызвать функцию doOperation(), компилятор заглянул в глобальное пространство имен в поисках определения doOperation(). Однако, поскольку ни одна из наших версий doOperation() не находится в глобальном пространстве имен, компилятор не смог найти определение doOperation() вообще!

Существует два разных способа сообщить компилятору, какую версию doOperation() следует использовать: через оператор разрешения области видимости или с помощью using-стейтментов (о них мы поговорим на следующем уроке).

Доступ к пространству имен через оператор разрешения области видимости (::)

Первый способ указать компилятору искать идентификатор в определенном пространстве имен — это использовать название необходимого пространства имен вместе с оператором разрешения области видимости (::) и требуемым идентификатором.

Например, сообщим компилятору использовать версию doOperation() из пространства имен Boo:

Результат:

9

Если бы мы захотели использовать версию doOperation() из пространства имен Doo:

Результат:

1

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

Результат:

9
1

Также этот оператор можно использовать без какого-либо префикса (например, ::doOperation). В таком случае мы ссылаемся на глобальное пространство имен.

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

Допускается объявление пространств имен в нескольких местах (либо в нескольких файлах, либо в нескольких местах внутри одного файла). Все, что находится внутри одного блока имен, считается частью только этого блока.

add.h:

subtract.h:

main.cpp:

Все работает, как нужно.

Стандартная библиотека C++ широко использует эту особенность, поскольку все заголовочные файлы, которые находятся в ней, реализуют свой функционал внутри пространства имен std.

Псевдонимы и вложенные пространства имен

Одни пространства имен могут быть вложены в другие пространства имен. Например:

Обратите внимание, поскольку Doo находится внутри Boo, то доступ к g_x осуществляется через Boo::Doo::g_x.

Так как это не всегда удобно и эффективно, то C++ позволяет создавать псевдонимы для пространств имен:

Стоит отметить, что пространства имен в C++ не были разработаны, как способ реализации информационной иерархии — они были разработаны в качестве механизма предотвращения возникновения конфликтов имен. Как доказательство этому, вся Стандартная библиотека шаблонов находится в единственном пространстве имен std::.

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

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

динамическая идентификация типа данных (англ. run-time type information, run-time type identification, RTTI) — механизм в некоторых языках программирования, который позволяет определить тип данных переменной или объекта во время выполнения программы.

* RTTI позволяет программам, которые манипулируют объектами через указатели или ссылки на базовые классы, получить истинный производный тип адресуемого объекта. Для поддержки RTTI в языке C++ есть два оператора: оператор dynamic_cast поддерживает преобразования типов во время выполнения, обеспечивая безопасную навигацию по иерархии классов. Он позволяет трансформировать указатель на базовый класс в указатель на производный от него, а также преобразовать l-значение, ссылающееся на базовый класс, в ссылку на производный, но только в том случае, если это завершится успешно;

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

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

19.1.1. Оператор dynamic_cast

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

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



class employee {

public:

virtual int salary();

};


class manager : public employee {

public:

int salary();

};


class programmer : public employee {

public:

int salary();

};


void company::payroll( employee *pe ) {

// используется pe-salary()

}

В компании есть разные категории служащих. Параметром функции-члена payroll() класса company является указатель на объект employee, который может адресовать один из типов manager или programmer. Поскольку payroll() обращается к виртуальной функции-члену salary(), то вызывается подходящая замещающая функция, определенная в классе manager или programmer, в зависимости от того, какой объект адресован указателем.

Допустим, класс employee перестал удовлетворять нашим потребностям, и мы хотим его модифицировать, добавив еще одну функцию-член bonus(), используемую совместно с salary() при расчете платежной ведомости. Для этого нужно включить новую функцию-член в классы, составляющие иерархию employee:



class employee {

public:

virtual int salary(); // ca?ieaoa

virtual int bonus(); // i?aiey

};


class manager : public employee {

public:

int salary();

};


class programmer : public employee {

public:

int salary();

int bonus();

};


void company::payroll( employee *pe ) {

// eniieucoaony pe-salary() e pe-bonus()

}

Если параметр pe функции payroll() указывает на объект типа manager, то вызывается виртуальная функция-член bonus() из базового класса employee, поскольку в классе manager она не замещена. Если же pe указывает на объект типа programmer, то вызывается виртуальная функция-член bonus() из класса programmer.

После добавления новых виртуальных функций в иерархию классов придется перекомпилировать все функции-члены. Добавить bonus() можно, если у нас есть доступ к исходным текстам функций-членов в классах employee, manager и programmer. Однако если иерархия была получена от независимого поставщика, то не исключено, что в нашем распоряжении имеются только заголовочные файлы, описывающие интерфейс библиотечных классов и объектные файлы с их реализацией, а исходные тексты функций-членов недоступны. В таком случае перекомпиляция всей иерархии невозможна.

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

Этот оператор применяется для получения указателя на производный класс, чтобы иметь возможность работать с теми его элементами, которые по-другому не доступны. Предположим, что мы расширяем библиотеку за счет добавления новой функции-члена bonus() в класс programmer. Ее объявление можно включить в определение programmer, находящееся в заголовочном файле, а саму функцию определить в одном из своих исходных файлов:



class employee {

public:

virtual int salary();

};


class manager : public employee {

public:

int salary();

};


class programmer : public employee {

public:

int salary();

int bonus();

};

Напомним, что payroll() принимает в качестве параметра указатель на базовый класс employee. Мы можем применить оператор dynamic_cast для получения указателя на производный programmer и воспользоваться им для вызова функции-члена bonus():



void company::payroll( employee *pe )

{

programmer *pm = dynamic_cast programmer* ( pe );


// anee pe oeacuaaao ia iauaeo oeia programmer,

// oi dynamic_cast auiieieony oniaoii e pm aoaao

// oeacuaaou ia ia?aei iauaeoa programmer

if ( pm ) {

// eniieuciaaou pm aey auciaa programmer::bonus()

}

// anee pe ia oeacuaaao ia iauaeo oeia programmer,

// oi dynamic_cast auiieieony iaoaa?ii

// e pm aoaao niaa??aou 0

else {

// eniieuciaaou ooieoee-?eaiu eeanna employee

}

}

Оператор

dynamic_cast

приводит свой операнд pe к типу programmer*. Преобразование будет успешным, если pe ссылается на объект типа programmer, и неудачным в противном случае: тогда результатом dynamic_cast будет 0.

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

Если в предыдущем примере pe действительно указывает на объект типа programmer, то операция dynamic_cast завершится успешно и pm будет инициализирован указателем на объект типа programmer. В противном случае pm получит значение 0. Проверив значение pm, функция company::payroll() может узнать, указывает ли pm на объект programmer. Если это так, то она вызывает функцию-член programmer::bonus() для вычисления премии программисту. Если же dynamic_cast завершается неудачно, то pe указывает на объект типа manager, а значит, необходимо применить более общий алгоритм расчета, не использующий новую функцию-член programmer::bonus().

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

Одна из возможных ошибок – это работа с результатом dynamic_cast без предварительной проверки на 0: нулевой указатель нельзя использовать для адресации объекта класса. Например:

void company::payroll( employee *pe )

{

programmer *pm = dynamic_cast( pe );


// iioaioeaeuiay ioeaea: pm eniieucoaony aac i?iaa?ee cia?aiey

static int variablePay = 0;

variablePay += pm-bonus();

// ...

}

Результат, возвращенный dynamic_cast, всегда следует проверять, прежде чем использовать в качестве указателя. Более правильное определение функции company::payroll() могло бы выглядеть так:

void company::payroll( employee *pe )

{

// auiieieou dynamic_cast e i?iaa?eou ?acoeuoao

if ( programmer *pm = dynamic_cast( pe ) ) {

// eniieuciaaou pm aey auciaa programmer::bonus()

}

else {

// eniieuciaaou ooieoee-?eaiu eeanna employee

}

}

Результат операции dynamic_cast используется для инициализации переменной pm внутри условного выражения в инструкции if. Это возможно, так как объявления в условиях возвращают значения. Ветвь, соответствующая истинности условия, выполняется, если pm не равно нулю: мы знаем, что операция dynamic_cast завершилась успешно и pe указывает на объект programmer. В противном случае результатом объявления будет 0 и выполняется ветвь else. Поскольку теперь оператор и проверка его результата находятся в одной инструкции программы, то невозможно случайно вставить какой-либо код между выполнением dynamic_cast и проверкой, так что pm будет использоваться только тогда, когда содержит правильный указатель.

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

dynamic_cast Type & &( lval )

где Type& – это целевой тип преобразования, а lval – l-значение типа базового класса. Операнд lval успешно приводится к типу Type& только в том случае, когда lval действительно относится к объекту класса, для которого один из производных имеет тип Type.

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

if ( programmer *pm = dynamic_cast programmer* ( pe ) )

нельзя переписать в виде

if ( programmer &pm = dynamic_cast& programmer& &( pe ) )

Для извещения об ошибке в случае приведения к ссылочному типу оператор dynamic_cast возбуждает исключение. Следовательно, предыдущий пример можно записать так:

#include typeinfo

void company::payroll( employee &re )

{

try {

programmer &rm = dynamic_cast& programmer & &( re );

// eniieuciaaou rm aey auciaa programmer::bonus()

}

catch ( std::bad_cast ) {

// eniieuciaaou ooieoee-?eaiu eeanna employee

}

}

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

Когда следует употреблять ссылочный вариант dynamic_cast вместо указательного? Это зависит только от желания программиста. При его использовании игнорировать ошибку приведения типа и работать с результатом без проверки (как в указательном варианте) невозможно; с другой стороны, применение исключений увеличивает накладные расходы во время выполнения программы

Вау!! 😲 Ты еще не читал? Это зря!

В общем, мой друг ты одолел чтение этой статьи об пространство имен. Работы впереди у тебя будет много. Смело пиши комментарии, развивайся и счастье окажется в твоих руках. Надеюсь, что теперь ты понял что такое пространство имен, динамическая идентификация типов, динамическая идентификация типа данных, run-time type information, run-time type identification, rtti и для чего все это нужно, а если не понял, или есть замечания, то не стесняйся, пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории С++ (C plus plus)

создано: 2015-12-20
обновлено: 2021-12-19
132401



Рейтиг 9 of 10. count vote: 2
Вы довольны ?:


Поделиться:

Найди готовое или заработай

С нашими удобными сервисами без комиссии*

Как это работает? | Узнать цену?

Найти исполнителя
$0 / весь год.
  • У вас есть задание, но нет времени его делать
  • Вы хотите найти профессионала для выплнения задания
  • Возможно примерение функции гаранта на сделку
  • Приорететная поддержка
  • идеально подходит для студентов, у которых нет времени для решения заданий
Готовое решение
$0 / весь год.
  • Вы можите продать(исполнителем) или купить(заказчиком) готовое решение
  • Вам предоставят готовое решение
  • Будет предоставлено в минимальные сроки т.к. задание уже готовое
  • Вы получите базовую гарантию 8 дней
  • Вы можете заработать на материалах
  • подходит как для студентов так и для преподавателей
Я исполнитель
$0 / весь год.
  • Вы профессионал своего дела
  • У вас есть опыт и желание зарабатывать
  • Вы хотите помочь в решении задач или написании работ
  • Возможно примерение функции гаранта на сделку
  • подходит для опытных студентов так и для преподавателей



Комментарии


Оставить комментарий
Если у вас есть какое-либо предложение, идея, благодарность или комментарий, не стесняйтесь писать. Мы очень ценим отзывы и рады услышать ваше мнение.
To reply

С++ (C plus plus)

Термины: С++ (C plus plus)