зачем нужен using namespace std c
Почему с using namespace std; может быть очень плохо
То, что написано ниже, для многих квалифицированных C++ разработчиков будет прекрасно известным и очевидным, но тем не менее, я периодически встречаю using namespace std; в коде различных проектов, а недавно в нашумевшей статье про впечатления от высшего образования было упомянуто, что студентов так учат писать код в вузах, что и сподвигло меня написать эту заметку.
Для чего вообще придумали пространства имен в C++? Когда какие-то две сущности (типы, функции, и т.д.) имеют идентификаторы, которые могут конфликтовать друг с другом при совместном использовании, C++ позволяет объявлять пространства с помощью ключевого слова namespace. Всё, что объявлено внутри пространства имен, принадлежит только этому пространству имен (а не глобальному). Используя using мы вытаскиваем сущности какого-либо пространства имен в глобальный контекст.
А теперь посмотрим, к чему это может привести.
Допустим, вы используете две библиотеки под названием Foo и Bar и написали в начале файла что-то типа
. таким образом вытащив всё, что есть в foo:: и в bar:: в глобальное пространство имен.
Все работает нормально, и вы можете без проблем вызвать Blah() из Foo и Quux() из Bar. Но однажды вы обновляете библиотеку Foo до новой версии Foo 2.0, которая теперь еще имеет в себе функцию Quux().
Теперь у вас конфликт: и Foo 2.0, и Bar импортируют Quux() в ваше глобальное пространство имен. В лучшем случае это вызовет ошибку на этапе компиляции, и исправление этого потребует усилий и времени.
А вот если бы вы явно указывали в коде метод с его пространством имен, а именно, foo::Blah() и bar::Quux(), то добавление foo::Quux() не было бы проблемой.
Но всё может быть даже хуже!
В библиотеку Foo 2.0 могла быть добавлена функция foo::Quux(), про которую компилятор по ряду причин посчитает, что она однозначно лучше подходит для некоторых ваших вызовов Quux(), чем bar::Quux(), вызывавшаяся в вашем коде на протяжении многих лет. Тогда ваш код все равно скомпилируется, но будет молча вызывать неправильную функцию и делать бог весть что. И это может привести к куче неожиданных и сложноотлаживающихся ошибок.
Имейте в виду, что пространство имен std:: имеет множество идентификаторов, многие из которых являются очень распространенными (list, sort, string, iterator, swap), которые, скорее всего, могут появиться и в другом коде, либо наоборот, в следущей версии стандарта C++ в std добавят что-то, что совпадет с каким-то из идентификаторов в вашем существующем коде.
Если вы считаете это маловероятным, то посмотрим на реальные примеры со stackoverflow:
Вот тут был задан вопрос о том, почему код возвращает совершенно не те результаты, что от него ожидает разработчик. По факту там происходит именно описанное выше: разработчик передает в функцию аргументы неправильного типа, но это не вызывает ошибку компиляции, а компилятор просто молча использует вместо объявленной выше функции distance() библиотечную функцию std::distance() из std:: ставшего глобальным неймспейсом.
Второй пример на ту же тему: вместо функции swap() используется std::swap(). Опять же, никакой ошибки компиляции, а просто неправильный результат работы.
Так что подобное происходит гораздо чаще, чем кажется.
Пространства имен (C++)
В следующем примере показано объявление пространства имен и продемонстрированы три способа доступа к членам пространства имен из кода за его пределами.
Использование полного имени:
Чтобы добавить в область видимости один идентификатор, используйте объявление using:
Чтобы добавить в область видимости все идентификаторы пространства имен, используйте директиву using:
директивы using
using Директива позволяет использовать все имена в namespace для использования без имени пространства имен в качестве явного квалификатора. Использование директивы using в файле реализации (т. е. *. cpp) при использовании нескольких различных идентификаторов в пространстве имен; Если вы используете только один или два идентификатора, рассмотрите использование объявления using, чтобы привести эти идентификаторы в область, а не все идентификаторы в пространстве имен. Если локальная переменная имеет такое же имя, как и переменная пространства имен, то переменная пространства имен будет скрытой. Создавать переменную пространства имен с те же именем, что и у глобальной переменной, является ошибкой.
Директиву using можно поместить в верхнюю часть CPP-файла (в области видимости файла) или внутрь определения класса или функции.
Без особой необходимости не размещайте директивы using в файлах заголовков (*.h), так как любой файл, содержащий этот заголовок, добавит все идентификаторы пространства имен в область видимости, что может вызвать скрытие или конфликты имен, которые очень трудно отлаживать. В файлах заголовков всегда используйте полные имена. Если эти имена получаются слишком длинными, используйте псевдоним пространства имен для их сокращения. (См. ниже.)
Объявление пространств имен и их членов
Как правило, пространство имен объявляется в файле заголовка. Если реализации функций находятся в отдельном файле, определяйте имена функций полностью, как показано в следующем примере.
Реализации функций в контосодата. cpp должны использовать полное имя, даже если поместить using директиву в начало файла:
Пространство имен может быть объявлено в нескольких блоках в одном файле и в нескольких файлах. Компилятор соединит вместе все части во время предварительной обработки и полученное в результате пространство имен будет содержать все члены, объявленные во всех частях. Примером этого является пространство имен std, которое объявляется в каждом из файлов заголовка в стандартной библиотеке.
Члены именованного пространства имен могут определяться за его границами, если они объявлены путем явной квалификации определяемого пространства имен. Однако определение должно располагаться после точки объявления в пространстве имен, окружающем то пространство имен, где находится объявление. Пример:
Эта ошибка может возникнуть, когда члены пространства имен объявляются в нескольких файлах заголовка и эти заголовки не включены в правильном порядке.
Глобальное пространство имен
Пространство имен std
Вложенные пространства имен
Пространства имен могут быть вложенными. Обычное вложенное пространство имен имеет неполный доступ к членам родительского элемента, но родительские элементы не имеют неполного доступа к вложенному пространству имен (если только оно не объявлено как встроенное), как показано в следующем примере:
Обычные вложенные пространства имен можно использовать для инкапсуляции данных о внутренней реализации, которые не являются частью открытого интерфейса родительского пространства имен.
Встроенные пространства имен (C++ 11)
В отличие от обычных вложенных пространств имен члены встроенного пространства имен обрабатываются как члены родительского пространства имен. Эта особенность позволяет выполнять поиск перегруженных функций с зависимостью от аргументов среди функции, которые имеют перегрузки в родительском и вложенном встроенном пространстве имен. Это также позволяет объявлять специализации в родительском пространстве имен для шаблонов, объявленных во встроенном пространстве имен. В следующем примере показано, как внешний код привязывается к встроенному пространству имен по умолчанию.
В следующем примере показано, как можно объявить специализацию в родительском пространстве имен шаблона, объявленного во встроенном пространстве имен.
Встроенные пространства имен можно использовать как механизм управления версиями для управления изменениями в открытом интерфейсе библиотеки. Например, можно создать одно родительское пространство имен и инкапсулировать каждую версию интерфейса в своем собственном пространстве имен, вложенном в родительское. Пространство имен, которое содержит самую последнюю или основную версию, квалифицируется как встроенное и поэтому представляется так, будто оно является непосредственным членом родительского пространства имен. Клиентский код, вызывающий Parent::Class, автоматически привязывается к новому коду. Клиенты, которые предпочитают использовать старую версию, могут по-прежнему получить доступ к ней, используя полный путь к вложенному пространству имен, содержащему данный код.
Ключевое слово inline должно применяться к первому объявлению пространства имен в единице компиляции.
Псевдонимы пространств имен
Имена пространств имен должны быть уникальными, из-за чего зачастую они получаются не слишком короткими. Если длина имени затрудняет чтение кода или утомительно вводить файл заголовка, где нельзя использовать директивы using, можно создать псевдоним пространства имен, который служит аббревиатурой для фактического имени. Пример:
анонимные или безымянные пространства имен
Вы можете создать явное пространство имен, но не присвоить ему имя.
Это называется безымянным или анонимным пространством имен, и его можно использовать, если нужно сделать объявления переменных невидимыми для кода в других файлах (т. е. обеспечить их внутреннюю компоновку) без создания именованного пространства имен. Весь код, находящийся в том же файле, может видеть идентификаторы в безымянном пространстве имен, но эти идентификаторы, а также само пространство имен, будет невидимым за пределами этого файла или, точнее, вне блока перевода.
Using namespace std что это такое и как используется в Visual C++
Отвечая на вопрос, что такое using namespace std для началае следует отметить, что переводе с английского описываемый термин означает пространство имени, являющиеся областью декларации, необходимо для определения различных идентификационных форм: функций и зависимых/независимых переменных.
Благодаря ей не происходит конфликта между именами, поскольку случаются ситуации, когда несколько переменных принимают одинаковые значения. Как правило, это происходит в том случае, когда созданы разные библиотеки.
Идентификаторы находятся в свободном доступе по отношению друг к другу. Они получают свободный доступ к независимым членам при эксплуатации полного именного формата.
Для этого крайне важно, чтобы объект содержал полную форму наименования using namespace std. Это важно, чтобы визуально понять, каким образом выглядит объявление, располагаясь в имени пространства.
Содержание:
На изображении представлено несколько вариаций доступа к кодировкам, располагающихся в пределах и за его ограждениями:
Директива Using
Using директива разрешает эксплуатацию всех имеющихся имен, которые включены в пространство имени.
При этом указывать квалификатор нет необходимости.
В том случае, когда существует только пару имен, актуальным будет создание обыкновенного имени.
Тогда можно добавить только необходимые идентификаторы, а остальные не трогать.
Важно отметить, что если наименование локальной переменной с основной будут совпадать, то в таком случае первая будет находится в скрытом доступе.
Стоит принять во внимание, что создание переменных с одинаковым наименованием является недопустимым действием.
Совет! Для удобства использования, using директива может быть расположена в верхушке файла формата cpp., либо наоборот, помещается внутрь созданной библиотеки.
Чтобы обеспечить для себя максимально комфортные условия работы, можно поработать над размещением необходимых файлов.
Если нет крайней необходимости, то директиву using не стоит размещать в заголовках файлом формата H.
Это связано с тем, что при данном действии все идентификаторы станут активными в поле видимости, повышая вероятность возникновения конфликтности некоторых наименований.
Для файлов оптимальным решением станет использование полного наименования.
В том случае, когда они получаются чересчур длинными, можно использовать сокращение в виде псевдонимов.
Читайте также:
Объявления в именном пространстве
Принято размещать объявления в виде названий файлов. В том случае, когда исполнение заданных функций размещено в отдельной библиотеке или файле, важно определить полное наименование.
Чтобы понимать, о каких действиях идет речь, стоит ознакомиться со следующим изображением:
Чтобы реализовать функцию contosodata формата cpp., также важно использовать полное наименование и в том случае, когда директива стоит в самом начале:
Using namespace std. может содержать объявления сразу в нескольких разделах, находящихся в одном и том же файле.
За счет компилятора происходит объединение всех элементов, пока происходит обработка данных.
Далее пространство будет включать все доступные имена и члены, которые были заявлены во всех разделах.
Так, например, std., как правило, объявляется во всех заголовках доступных файлов, располагающихся в доступных библиотеках стандартного типа.
Что касается определения, то оно должно быть после объявления в пространстве имени, где оно создано.
В качестве наглядного примера обращаем внимание на следующее изображение:
Чаще всего такая ошибка появляется в том случае, когда нарушается порядок определения, либо составляющие части полного наименования включены сразу в доступных объектах.
Когда идентификационные файлы не заявляется в определенном именном пространстве, он формально входит в пространство глобального типа.
Совет! Если нет острой необходимости, то рекомендуется избегать включения членов в пространство глобального типа.
Существенным исключением из правил может быть только главная (main) опция, подразумевающая обязательное включение в обширном пространстве.
Чтобы создать идентификатор глобального типа, необходимо задействовать соответствующий функционала видимости, ввиде полного наименования.
Подобное действие поможет создать отличительное свойство одного идентификатора от других имеющихся, которые находятся в другом именном пространстве.
6.12 – Объявления using и директивы using
Вы, наверное, видели эту программу во многих учебниках и учебных руководствах:
Некоторые старые компиляторы также начинают новые проекты с аналогичной программы.
Если вы это видите, бегите. Возможно, ваш учебник, руководство или компилятор устарели. В этом уроке мы выясним, почему.
Краткий урок истории
Перенесемся в сегодняшний день – если вы много используете стандартную библиотеку, набирание std:: перед всем, что вы используете из стандартной библиотеки, может стать повторяющейся рутиной, а в некоторых случаях может затруднить чтение вашего кода.
Но сначала давайте определим два термина.
Полные и неполные имена
Имя может быть полным или неполным.
Полное имя – это имя, которое включает в себя связанную область видимости. Чаще всего имена дополняются пространством имен с помощью оператора разрешения области видимости ( :: ). Например:
Для продвинутых читателей
Неполное имя – это имя, которое не включает в себя квалификатор области видимости. Например, cout и x являются неполными именами, поскольку они не включают связанную область видимости.
Объявления using
Директивы using
Вот снова наша программа Hello World с директивой using в строке 5:
Директивы using – это решение, которое было предоставлено для баз старого кода до пространства имен, которые использовали неполные имена для функций стандартной библиотеки. Вместо того, чтобы вручную обновлять каждое неполное имя до полного имени (что было рискованно), можно было бы разместить одну директиву using (т.е. using namespace std; ) в верхней части каждого файла, и все имена, которые были перемещены в пространство имен std всё еще можно было использовать неполными.
Проблемы с директивами using
Однако для современного кода C++ директивы using дают немного выгоды (экономия на вводе текста) по сравнению с риском.
Поскольку директивы using импортируют все имена из пространства имен (потенциально включая множество имен, которые вы никогда не будете использовать), вероятность возникновения конфликтов имен значительно возрастает (особенно, если вы импортируете пространство имен std ).
Для наглядности рассмотрим пример, в котором директивы using вызывают неоднозначность:
Вот еще один более коварный пример:
или использовалось объявление using вместо директивы using :
тогда наша программа вообще не имела бы никаких проблем.
Даже если директивы using не вызывают конфликтов имен сегодня, они делают код более уязвимым для будущих конфликтов. Например, если ваш код включает директиву using для какой-либо библиотеки, которая затем обновляется, все новые имена, представленные в обновленной библиотеке, теперь являются кандидатами на конфликты имен с вашим существующим кодом.
Может возникнуть и более коварная проблема. В обновленной библиотеке может появиться функция, которая не только имеет то же имя, но и лучше подходит для некоторых вызовов функций. В таком случае компилятор может решить отдать предпочтение новой функции, и поведение вашей программы неожиданно изменится.
Рассмотрим следующую программу:
Когда компилятор встречает вызов функции, он должен определить, с каким определением функции он должен сопоставить этот вызов. При выборе функции из набора потенциально совпадающих функций он предпочтет ту функцию, которая не требует преобразования аргументов, а не ту, которая требует преобразования аргументов. Поскольку литерал 0 принадлежит целочисленному типу, C++ предпочтет сопоставить someFcn(0) с недавно добавленной someFcn(int) (без преобразований), а не с someFcn(double) (требуется преобразование из int в double). Это вызывает неожиданное изменение результатов работы нашей программы.
Этого бы не произошло, если бы мы использовали объявление using или явный квалификатор области видимости.
Область видимости объявлений и директив using
Если объявление using или директива using используется в блоке, имена применимы только в этом блоке (они следуют обычным правилам области видимости блока). Это хорошо, поскольку снижает вероятность возникновения конфликтов имен внутри этого блока.
Если объявление using или директива using используются в глобальном пространстве имен, имена применимы ко всему остальному файлу (они имеют область видимости файла).
Отмена или замена инструкции using
После объявления инструкции using ее невозможно отменить или заменить ее другой инструкцией using в той области видимости, в которой она была объявлена.
Лучшее, что вы можете сделать, – это намеренно с самого начала ограничить область видимости инструкции using с помощью правил области видимости блока.
Конечно, всей этой головной боли можно избежать, в первую очередь, используя явным образом оператор разрешения области видимости ( :: ).
Лучшие практики для инструкций using
В современном C++ на самом деле нет места для использования директив. Они увеличивают вероятность коллизий имен сейчас и в будущем и могут вызывать более коварные проблемы. Хотя во многих учебниках и руководствах они широко используются, использования директив лучше вообще избегать.
Объявления using обычно считаются безопасными для использования внутри блоков. Ограничьте их использование в глобальном пространстве имен файлов исходного кода и никогда не используйте их в глобальном пространстве имен заголовочных файлов.
Лучшая практика
Урок №53. Пространства имен
Этот урок является продолжением урока №24.
Конфликт имен
Конфликт имен возникает, когда два одинаковых идентификатора находятся в одной области видимости, и компилятор не может понять, какой из этих двух следует использовать в конкретной ситуации. Компилятор или линкер выдаст ошибку, так как у них недостаточно информации, чтобы решить эту неоднозначность. Как только программы увеличиваются в объемах, количество идентификаторов также увеличивается, следовательно, увеличивается и вероятность возникновения конфликтов имен.
Рассмотрим пример такого конфликта. boo.h и doo.h — это заголовочные файлы с функциями, которые выполняют разные вещи, но имеют одинаковые имена и параметры.
Если boo.h и doo.h скомпилировать отдельно, то всё пройдет без инцидентов. Однако, соединив их в одной программе, мы подключим две разные функции, но с одинаковыми именами и параметрами, в одну область видимости (глобальную), а это, в свою очередь, приведет к конфликту имен. В результате, компилятор выдаст ошибку. Для решения подобных проблем и добавили в язык С++ такую концепцию, как пространства имен.
Что такое пространство имен?
Пространство имен определяет область кода, в которой гарантируется уникальность всех идентификаторов. По умолчанию, глобальные переменные и обычные функции определены в глобальном пространстве имен. Например:
Глобальная переменная g_z и функция boo() определены в глобальном пространстве имен.
В примере, приведенном выше, при подключении файлов boo.h и doo.h обе версии doOperation() были включены в глобальное пространство имен, из-за чего, собственно, и произошел конфликт имен.
Чтобы избежать подобных ситуаций, когда два независимых объекта имеют идентификаторы, которые могут конфликтовать друг с другом при совместном использовании, язык C++ позволяет объявлять собственные пространства имен через ключевое слово namespace. Всё, что объявлено внутри пользовательского пространства имен, — принадлежит только этому пространству имен (а не глобальному).
Перепишем заголовочные файлы из вышеприведенного примера, но уже с использованием namespace:
Результатом будет еще одна ошибка:
C:\VCProjects\Test.cpp(15) : error C2065: ‘doOperation’ : undeclared identifier
Случилось так, что когда мы попытались вызвать функцию doOperation(), компилятор заглянул в глобальное пространство имен в поисках определения doOperation(). Однако, поскольку ни одна из наших версий doOperation() не находится в глобальном пространстве имен, компилятор не смог найти определение doOperation() вообще!
Существует два разных способа сообщить компилятору, какую версию doOperation() следует использовать: через оператор разрешения области видимости или с помощью using-стейтментов (о них мы поговорим на следующем уроке).
Доступ к пространству имен через оператор разрешения области видимости (::)
Первый способ указать компилятору искать идентификатор в определенном пространстве имен — это использовать название необходимого пространства имен вместе с оператором разрешения области видимости ( :: ) и требуемым идентификатором.
Например, сообщим компилятору использовать версию doOperation() из пространства имен Boo :