«Using namespace» в заголовках C ++

119

На всех наших курсах по C ++ все учителя всегда ставят using namespace std;сразу после #includes в своих .hфайлах. Мне это кажется опасным, поскольку с тех пор, включив этот заголовок в другую программу, я получу пространство имен, импортированное в мою программу, возможно, не осознавая, не намереваясь или не желая этого (включение заголовка может быть очень глубоко вложенным).

Итак, мой вопрос двоякий: я прав, что using namespaceне следует использовать в файлах заголовков, и / или есть какой-то способ отменить это, например:

//header.h
using namespace std {
.
.
.
}

Еще один вопрос в том же духе: должен ли файл заголовка #includeвсе заголовки, которые .cppнужны соответствующему файлу, только те, которые необходимы для определений заголовков, и позволить .cppфайлу #includeостальное или ничего и объявить все, что ему нужно, как extern?
Причина вопроса такая же, как и выше: я не хочу сюрпризов при включении .hфайлов.

Кроме того, если я прав, это распространенная ошибка? Я имею в виду программирование в реальном мире и «настоящие» проекты.

Спасибо.

Барух
источник
3
в качестве примечания: если вы получаете коллизии имен из-за using namespaceоператоров, вы можете использовать полное имя для решения проблемы.
Мариус Бансила

Ответы:

115

Вам определенно НЕ следует использовать using namespaceв заголовках именно по той причине, по которой вы говорите, что это может неожиданно изменить значение кода в любых других файлах, которые включают этот заголовок. Невозможно отменить действие, using namespaceчто является еще одной причиной его опасности. Я обычно просто использую grepили что-то подобное, чтобы убедиться, что using namespaceон не вызывается в заголовках, вместо того, чтобы пытаться что-то более сложное. Вероятно, это тоже отмечают средства проверки статического кода.

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

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

Марк Б
источник
2
можем ли мы использовать usingинструкции в наших .cppфайлах? в 3rdPartyLib::BigClassName<3rdPartyLib::AnotherBigName,3rdPartyLib::AnotherBigName>::Iterators является смертью до кончиков пальцев.
Кристофер
1
и как нам оптимизировать templateфункции - которые должны быть в заголовках? typedefs?
Кристофер
1
@donlan, похоже, вы долгое время не получали ответа ... Да, вы можете без особого беспокойства использовать usingоператоры в .cppфайлах, потому что область действия будет ограничена только этим файлом, но никогда не делайте этого перед #includeоператором. Что касается шаблонных функций, определенных в заголовках, к сожалению, я не знаю хорошего решения, кроме простого написания пространства имен ... Возможно, вы могли бы поместить usingобъявление в отдельную область видимости { /* using statement in between brackets */ }, что, по крайней мере, предотвратило бы его выход из текущего файла ,
tjwrona1992
26

Пункт 59 в книге Саттера и Александреску «Стандарты программирования C ++: 101 правила, рекомендации и передовой опыт» :

59. Не записывайте использование пространств имен в заголовочный файл или перед #include.

Пространства имен usingпредназначены для вашего удобства, а не для того, чтобы вы навязывали их другим: никогда не пишите usingдекларацию или usingдирективу перед #includeдирективой.

Следствие: в файлах заголовков не записывайте usingдирективы или usingобъявления на уровне пространства имен ; вместо этого явно уточняйте все имена по пространству имен.

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

using Декларация приносит приятель. using Директива приносит во всех приятелях в пространстве имен. Ваши учителя using namespace std;используют директиву using.

А если серьезно, у нас есть пространства имен, чтобы избежать конфликта имен. Заголовочный файл предназначен для предоставления интерфейса. Большинство заголовков не зависит от того, какой код может их включать сейчас или в будущем. Добавление usingоператоров для внутреннего удобства в заголовок навязывает эти удобные имена всем потенциальным клиентам этого заголовка. Это может привести к конфликту имен. И это просто грубо.

Энди Томас
источник
12

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

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

Что касается пространств имен, я обычно использую явную область видимости пространства имен в своих файлах заголовков и помещаю только using namespaceв свои файлы cpp.

Майк О'Коннор
источник
1
как вы оптимизируете templateобъявление функции? что должно происходить в заголовке, нет?
Кристофер
6

Ознакомьтесь со стандартами кодирования Goddard Space Flight Center (для C и C ++). Это оказалось немного сложнее, чем раньше - см. Обновленные ответы на вопросы SO:

Стандарт кодирования GSFC C ++ гласит:

§3.3.7 Каждый файл заголовка должен содержать #includeфайлы, необходимые для компиляции, а не заставлять пользователей обращаться к #includeнужным файлам. #includesдолжен быть ограничен тем, что нужно заголовку; другое #includesследует поместить в исходный файл.

Первый из вопросов с перекрестными ссылками теперь включает цитату из стандарта кодирования GSFC C и обоснование, но суть остается прежней.

Джонатан Леффлер
источник
5

Вы правы, что using namespaceв шапке опасно. Я не знаю, как это исправить. Обнаружить это несложно, однако достаточно выполнить поиск using namespaceв файлах заголовков. По этой последней причине это нечасто в реальных проектах. Более опытные коллеги скоро будут жаловаться, если кто-то сделает что-то подобное.

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

Öö Tiib
источник
4

Ты прав. И любой файл должен включать только заголовки, необходимые для этого файла. Что касается "делать что-то неправильно в реальных проектах?" - о да!


источник
4

Как и все в программировании, прагматизм должен победить догматизм, ИМО.

Пока вы принимаете решение в масштабе всего проекта («Наш проект широко использует STL, и мы не хотим, чтобы все добавлялось с помощью std ::.»), Я не вижу в этом проблемы. В конце концов, единственное, чем вы рискуете, так это конфликтом имен, а с повсеместным распространением STL это вряд ли станет проблемой.

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

ijprest
источник
4

Что касается вопроса «Есть ли способ отменить [ usingобъявление]?»

Думаю, полезно отметить, что на usingобъявления влияет область видимости.

#include <vector>

{   // begin a new scope with {
    using namespace std;
    vector myVector;  // std::vector is used
}   // end the scope with }

vector myOtherVector;   // error vector undefined
std::vector mySTDVector // no error std::vector is fully qualified

Так что да. Ограничивая область действия usingобъявления, ее действие длится только в пределах этой области; он «отменяется», когда этот объем заканчивается.

Когда usingобъявление объявляется в файле за пределами любой другой области, оно имеет файловую область и влияет на все в этом файле.

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

YoungJohn
источник
2
Вы, кажется, единственный, кто понял реальный вопрос ... однако моя компиляция не очень довольна тем, что я использую замедление внутри класса.
rustypaper
Этот ответ можно было бы сделать еще лучше, объяснив проблему с идеей OP о том, как должна работать область видимости (например, namespaceматериал объявления) и как он на самом деле работает (например, переменная). {}включение его ограничивает его объем, {}после того как он ничего не делает с ним. Это случайный способ using namespaceглобального применения.
TafT
2

Я считаю, что вы можете безопасно использовать 'using' в заголовках C ++, если вы пишете свои объявления во вложенном пространстве имен следующим образом:

namespace DECLARATIONS_WITH_NAMESPACES_USED_INCLUDED
{
    /*using statements*/

    namespace DECLARATIONS_WITH_NO_NAMESPACES_USED_INCLUDED
    {
        /*declarations*/
    }
}

using namespace DECLARATIONS_WITH_NAMESPACES_USED_INCLUDED::DECLARATIONS_WITH_NO_NAMESPACES_USED_INCLUDED;

Это должно включать только то, что объявлено в 'DECLARATIONS_WITH_NO_NAMESPACES_USED_INCLUDED' без используемых пространств имен. Я тестировал его на компиляторе mingw64.

AnArrayOfFunctions
источник
Это полезная техника, которую я раньше не видел; Спасибо. Обычно меня устраивает использование полной квалификации и размещение usingобъявлений внутри определений функций там, где я могу, чтобы они не загрязняли пространства имен вне функции. Но теперь я хочу использовать определяемые пользователем литералы C ++ 11 в файле заголовка, и, согласно обычному соглашению, буквальные операторы защищены пространством имен; но я не хочу использовать их в списках инициализаторов конструктора, которые не входят в область, в которой я могу использовать объявление, не загрязняющее окружающую среду using. Так что это отлично подходит для решения этой проблемы.
Энтони Холл
Хотя неприятный побочный эффект этой модели является то , что любые классы , объявленных внутри пространства имен сокровенных будут отображаться в сообщениях об ошибках компилятора с полным именем: error: ... DECLARATIONS_WITH_NAMESPACES_USED_INCLUDED:: DECLARATIONS_WITH_NO_NAMESPACES_USED_INCLUDED::ClassName .... По крайней мере, это то, что со мной происходит в g ​​++.
Энтони Холл