Основы С++: Знакомство с пространствами имен. Как и почему возникают конфликты имен?

В С++ к именам выставляются определенные требования. Среди них мы обнаруживаем такое, как однозначность. Что это значит?

Рассмотрим на простом примере:

Представьте, что вы приехали в новый для вас город по своим делам. Вам нужно попасть по адресу: улица Яблоневая, дом 32. Вы вбиваете адрес в приложение такси, вызываете машину. Вас привозят по адресу. И вдруг оказывается, что по этому адресу вы не можете решить свои дела. Потому что в этом городе две Яблоневых улицы. Вы возмущены!

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

Рассмотрим эту программу из двух файлов, как пример конфликта имен:

В С++ к именам выставляются определенные требования. Среди них мы обнаруживаем такое, как однозначность. Что это значит?

Итак, создайте программу с одним файлом .cpp и перепишите в него код со скрина. Я назвал этот файл main. После чего добавьте новый файл в ваше решение, используйте вкладку исходные файлы в Visual Studio. Если вы пропустили статью по использованию нескольких файлов внутри решений IDE, почитайте об этом здесь.

В С++ к именам выставляются определенные требования. Среди них мы обнаруживаем такое, как однозначность. Что это значит?-2

Добавьте во второй файл этот код. Проверьте правильность того, что вы переписали со скринов в обоих файлах и попробуйте скомпилировать программу.

В С++ к именам выставляются определенные требования. Среди них мы обнаруживаем такое, как однозначность. Что это значит?-3

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

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

! Компоновщик выдаст ошибку, даже если функция с конфликтом имен не будет никогда вызываться, а просто будет существовать внутри кода.

Почему возникают конфликты имен?

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

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

О том, как включаются переменные через #include мы разберем в следующих статьях.

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

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

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

Когда вы определяете имя для любого идентификатора не принадлежащего конкретному пространству имен, такое имя по умолчанию принадлежит "глобальному пространству имен" или "глобальной области видимости".

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

Пространство имен std

Мы уже ни раз использовали std::cout и std::cin для того, чтобы напечатать текст и числа или принять их от пользователя. В самых первых статьях, я уже говорил о том, что запись std:: показывает принадлежность предопределенных переменных cout и cin к стандартной библиотеке. Иначе, мы можем говорить, что std:: показывает принадлежность cout и cin к пространству имен std.

Немного истории

Когда C++ был разработан, все идентификаторы из его стандартной библиотеки не требовали описания их принадлежности к пространству имен std. Программист мог использовать их без префикса std::

Однако, потенциально, всегда существовала возможность возникновения конфликта имен. И поводов для этого было множество.

Например, программист написал программу в одной версии C++ и использовал некое имя для собственной функции. Вскоре вышла новая версия языка, в которой имя для его функции стало идентично одному из имен стандартной библиотеки. И уже его коллеги или он сам столкнутся с конфликтом имен, запуская код в новой версии языка.

Но чаще, конфликты возникали гораздо быстрее, чем выйдет новая версия C++

Например, программисту достаточно было во время написания кода большой программы сначала создать функцию с именем идентичным имени одного из объектов стандартной библиотеки, а потом включить через #include это имя в свой проект. Вуаля! Получите конфликт имен.

Из-за этих проблем, вскоре для всех идентификаторов из стандартной библиотеки появилось пространство имен std (сокр. standart)

Теперь, например, если вы захотите назвать свою функцию cout, она не будет конфликтовать с std::cout

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

Явным квалификатором пространства имен называется использование префикса пространства имен каждый раз, когда мы хотим показать, что некоторое имя принадлежит определенному пространству имен, например: std::cout

Явная квалификация пространства имен std является самым безопасным способом использовать стандартную библиотеку языка. В случае с явным квалификатором мы однозначно указываем, какому пространству принадлежит конкретное имя.

Однако, в некоторой литературе по С++ вы можете встретить другой способ получения доступа к идентификаторам внутри пространства имен (сразу скажу, кхм, это плохой способ). Это использование специальной директивы using.

Более того, некоторые обучающие курсы по C++ в интернете, на которые приходят новички, все еще дают этот способ в процессе обучения.

В чем же коварство использования директивы using?

Для начала попробуем использовать эту директиву для написания программы, печатающей текст "Hello, world!".

В С++ к именам выставляются определенные требования. Среди них мы обнаруживаем такое, как однозначность. Что это значит?-4

Посмотрите, директива using перед функцией main (строка 3) позволяет нам неявно квалифицировать принадлежность cout к пространству имен std.

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

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

В С++ к именам выставляются определенные требования. Среди них мы обнаруживаем такое, как однозначность. Что это значит?-5

Почему в случае с квалификацией пространства имен с помощью using происходит конфликт имен? Для понимания этого, нам необходимо разобрать: А что же делает директива using namespace std в начале программы?

Директива using namespace std; говорит компилятору: Каждый раз, когда видишь имя идентификатора без принадлежности к какому либо локальному пространству имен, проверяй его на принадлежность к пространству имен std.

Программа, в которой мы определили функцию cout не будет компилироваться, потому что компилятор не может решить (а мы явно не указывали) что ему использовать: определенную пользователем функцию int cout или cout из пространства имен std.

Если разжевать подробнее, получив информацию от директивы using namespace std, компилятор начинает проверять все имена из глобального пространства имен.

Тут он сначала на строке находит функцию int cout, которая создана пользователем в глобальном пространстве имен и уже определена там. Хорошо, пусть и будет определена там.

Идя по строкам дальше, он обнаруживает инструкцию внутри функции main, которая гласит: cout << "Hello, world!; И вот тут начинается путаница.

С одной стороны, у компилятора уже есть функция cout, определенная в глобальном пространстве имен, а с другой появился еще один cout, без явной типизации int (значит это точно другой объект?), который он согласно директивы using проверил и нашел его принадлежность пространству имен std.

Но эти объекты оба называются cout? Что делать? Компилятор теперь не знает что ему делать. Программа не компилируется.

Вывод простой:
1. Используйте явную квалификацию для пространства имен (Пример: std::cout).
2. Бегите с курсов и выбросите учебники, в которых вас учат использовать директиву using для квалификации пространства имен.

Для чего на самом деле нужны директивы using, как они появились и для чего первоначально использовались мы поговорим в одной из статей. Но, честно говоря, будет это нескоро.

На сегодня это весь материал, который я хотел изложить и разобрать вместе с вами.

Понравился материал? Оставляйте лайк.

Если у вас остались вопросы, задавайте их в комментариях.

Подпишитесь, чтобы не пропустить выход новых статей.