Как правильно использовать пространства имен в C ++?

231

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

Как вы используете пространства имен в C ++? Вы создаете единое пространство имен для всего приложения или вы создаете пространства имен для основных компонентов? Если да, то как вы создаете объекты из классов в других пространствах имен?

Marius
источник

Ответы:

167

Пространства имен по сути являются пакетами. Их можно использовать так:

namespace MyNamespace
{
  class MyClass
  {
  };
}

Тогда в коде:

MyNamespace::MyClass* pClass = new MyNamespace::MyClass();

Или, если вы хотите всегда использовать определенное пространство имен, вы можете сделать это:

using namespace MyNamespace;

MyClass* pClass = new MyClass();

Редактировать: следуя тому, что сказал Бернхардруш , я склонен вообще не использовать синтаксис «using namespace x», я обычно явно указываю пространство имен при создании экземпляров моих объектов (т.е. первый пример, который я показал).

И, как вы спросили ниже , вы можете использовать столько пространств имен, сколько захотите.

Марк Инграм
источник
25
IMO лучше просто привыкнуть к префиксу stdпространства имен к символам, а не использовать его usingвообще. Так что я всегда пишу std::coutили std::stringсейчас, потому что так я их сейчас называю. Я бы никогда не написал cout.
Том Сэвидж
5
Хотя это очень верно std, я лично нашел это гораздо менее важным, когда вы имеете дело с небольшими библиотеками. Часто вы можете просто использовать using namespace FooBario;, особенно если вы используете значительное количество типов из библиотеки.
jkerian
4
@jkerian, я понимаю вашу точку зрения, но я не согласен, потому что конфликты имен (на мой взгляд), скорее всего, происходят именно из таких маленьких библиотек. Большинство людей стараются не называть классы / функции такими же, как в STL. Тем не менее, я согласен, что using namespace X;следует избегать в заголовочных файлах, если это возможно.
Алан Тьюринг
12
@LexFridman «Большинство людей стараются не называть классы / функции такими же, как в STL» - это ТАК НЕ ВЕРНО. Например, если бы я написал какой-то очень специализированный код ввода-вывода для какого-то странного оборудования, я бы никогда не использовал ничего, кроме как mylibrary::endlдля представления своей собственной специальной последовательности новой строки. Я имею в виду, зачем придумывать имена?
Мой компилятор по-прежнему не распознает пространство имен, хотя я хочу явно указать его и включить файл, в котором оно объявлено.
Bgenchel
116

Чтобы не говорить ничего, Марк Ингрэм уже сказал небольшой совет по использованию пространств имен:

Избегайте директивы using namespace в заголовочных файлах - это открывает пространство имен для всех частей программы, которые импортируют этот заголовочный файл. В файлах реализации (* .cpp) это обычно не представляет большой проблемы - хотя я предпочитаю использовать директиву "using namespace" на уровне функций.

Я думаю, что пространства имен в основном используются, чтобы избежать конфликтов имен - не обязательно для организации структуры кода. Я бы организовал программы на C ++ в основном с заголовочными файлами / файловой структурой.

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

Дополнительное примечание к директиве using: Некоторые люди предпочитают использовать «using» только для отдельных элементов:

using std::cout;  
using std::endl;
bernhardrusch
источник
2
Одно из преимуществ «использования пространства имен» на уровне функций, как вы предлагаете, а не на уровне файлов .cpp или на уровне блоков пространств имен {} в .cpp, заключается в том, что это очень помогает при сборках с единым модулем компиляции. «Использование пространства имен» является транзитивным и применяется к пространству имен A в отдельных блоках пространства имен A {} в одном и том же модуле, поэтому для сборок с единым модулем компиляции вы в конечном итоге быстро используете все, если они выполняются на уровне блоков файла или пространства имен.
иди
using std::cout; является декларацией об использовании
Константин
3
Можно ли использовать несколько имен из одного пространства имен в одном операторе? Что-то вроде using std::cout, std::endl;или даже using std::cout, endl;.
AlQuemist
Может быть нормально использовать using namespace xзаголовок, если он находится в другом пространстве имен. Это не то, что я бы рекомендовал в целом, но оно не загрязняет глобальное пространство имен.
Праксеолит
79

Винсент Роберт прав в своем комментарии. Как правильно использовать пространства имен в C ++? ,

Использование пространства имен

Пространства имен используются, по крайней мере, чтобы избежать конфликта имен. В Java это обеспечивается с помощью идиомы «org.domain» (поскольку предполагается, что никто не будет использовать ничего, кроме своего собственного доменного имени).

В C ++ вы можете дать пространство имен всему коду в вашем модуле. Например, для модуля MyModule.dll вы можете дать его коду пространство имен MyModule. Я видел в другом месте, кто-то использует MyCompany :: MyProject :: MyModule. Я думаю, это излишне, но в целом мне кажется правильным.

Используя "использование"

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

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

Самый безопасный способ использовать «использование» - это импортировать выбранные символы:

void doSomething()
{
   using std::string ; // string is now "imported", at least,
                       // until the end of the function
   string a("Hello World!") ;
   std::cout << a << std::endl ;
}

void doSomethingElse()
{
   using namespace std ; // everything from std is now "imported", at least,
                       // until the end of the function
   string a("Hello World!") ;
   cout << a << endl ;
}

Вы увидите много «использования пространства имен std;» в учебных или примерных кодах. Причина в том, чтобы уменьшить количество символов, чтобы облегчить чтение, а не потому, что это хорошая идея.

"использование пространства имен std;" обескуражен Скоттом Мейерсом (я не помню точно, какую книгу, но я могу найти ее в случае необходимости).

Композиция пространства имен

Пространства имен - это больше, чем пакеты. Другой пример можно найти в книге Бьярна Страуструпа «Язык программирования C ++».

В «Специальном издании», в 8.2.8 Состав пространства имен , он описывает, как вы можете объединить два пространства имен AAA и BBB в другое, называемое CCC. Таким образом, CCC становится псевдонимом для AAA и BBB:

namespace AAA
{
   void doSomething() ;
}

namespace BBB
{
   void doSomethingElse() ;
}

namespace CCC
{
   using namespace AAA ;
   using namespace BBB ;
}

void doSomethingAgain()
{
   CCC::doSomething() ;
   CCC::doSomethingElse() ;
}

Вы даже можете импортировать выбранные символы из разных пространств имен, чтобы создать свой собственный интерфейс пространства имен. Мне еще предстоит найти практическое применение этому, но в теории это круто.

paercebal
источник
Не могли бы вы уточнить, пожалуйста, "дайте пространство имен всему коду в вашем модуле"? Что является хорошей практикой для инкапсуляции в модуль. Например, у меня есть класс комплексных чисел и внешние функции, связанные с комплексными числами. Этот класс и эти две функции должны быть в одном пространстве имен?
Янп
74

Я не видел никакого упоминания об этом в других ответах, поэтому вот мои 2 канадских цента:

В разделе «Использование пространства имен» полезным является псевдоним пространства имен, позволяющий вам «переименовать» пространство имен, обычно, чтобы дать ему более короткое имя. Например, вместо:

Some::Impossibly::Annoyingly::Long:Name::For::Namespace::Finally::TheClassName foo;
Some::Impossibly::Annoyingly::Long:Name::For::Namespace::Finally::AnotherClassName bar;

ты можешь написать:

namespace Shorter = Some::Impossibly::Annoyingly::Long:Name::For::Namespace::Finally;
Shorter::TheClassName foo;
Shorter::AnotherClassName bar;
Эрик Маленфант
источник
55

Не слушайте, чтобы все люди говорили вам, что пространства имен - это просто пространства имен.

Они важны, потому что они рассматриваются компилятором для применения принципа интерфейса. В основном это можно объяснить на примере:

namespace ns {

class A
{
};

void print(A a)
{
}

}

Если вы хотите напечатать объект A, код будет таким:

ns::A a;
print(a);

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

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

namespace ns {

class A
{
};

}

void print(A a)
{
}

И ваш код может начать вызывать функцию print (a) где угодно. Теперь представьте, что спустя годы автор решает предоставить функцию print () лучше, чем ваша, потому что он знает внутренности своего класса и может сделать лучшую версию, чем ваша.

Затем авторы C ++ решили, что его версия функции print () должна использоваться вместо той, которая представлена ​​в другом пространстве имен, для соблюдения принципа интерфейса. И что это «обновление» функции print () должно быть максимально простым, а это означает, что вам не придется менять каждый вызов функции print (). Вот почему «интерфейсные функции» (функции в том же пространстве имен, что и у класса) можно вызывать без указания пространства имен в C ++.

И именно поэтому вы должны рассматривать пространство имен C ++ как «интерфейс», когда используете его, и иметь в виду принцип интерфейса.

Если вы хотите получить лучшее объяснение этого поведения, вы можете обратиться к книге « Исключительный C ++» от Херба Саттера.

Винсент Роберт
источник
23
Вы действительно должны изменить каждый вызов print (), если добавлен ns :: Print, но компилятор будет отмечать каждый вызов как неоднозначный. Молча переключиться на новую функцию было бы ужасной идеей.
Затмение
Теперь мне интересно, что @Vincent сказал, что вам придется изменить все вызовы на печать, если autor предоставит функцию ns :: Print (), что вы пытаетесь сказать? Что, когда автор добавил функцию ns :: Print (), вы можете просто удалить свою собственную реализацию? Или то, что вы просто добавите с помощью ns :: print (), используя-объявление? Или купол еще? Спасибо
Васька эль Гато
36

В больших проектах на C ++, которые я видел, едва ли использовалось более одного пространства имен (например, библиотеки Boost).

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

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

Другое использование (без каламбура) пространств имен, которое я часто использую, - это анонимное пространство имен:

namespace {
  const int CONSTANT = 42;
}

Это в основном так же, как:

static const int CONSTANT = 42;

Однако рекомендуется использовать анонимное пространство имен (вместо статического), чтобы код и данные были видны только в текущем модуле компиляции в C ++.


источник
13
Оба ваших примера эквивалентны, const int CONSTANT = 42;поскольку const верхнего уровня в области именного пространства уже подразумевает внутреннюю связь. Таким образом, вам не нужно анонимное пространство имен в этом случае.
Sellibitze
19

Также обратите внимание, что вы можете добавить в пространство имен. Это яснее с примером, я имею в виду, что вы можете иметь:

namespace MyNamespace
{
    double square(double x) { return x * x; }
}

в файле square.h, и

namespace MyNamespace
{
    double cube(double x) { return x * x * x; }
}

в файле cube.h. Это определяет одно пространство имен MyNamespace(то есть вы можете определить одно пространство имен для нескольких файлов).

OysterD
источник
11

В Java:

package somepackage;
class SomeClass {}

В C ++:

namespace somenamespace {
    class SomeClass {}
}

И используя их, Java:

import somepackage;

И C ++:

using namespace somenamespace;

Также полными именами являются «somepackge.SomeClass» для Java и «somenamespace :: SomeClass» для C ++. Используя эти соглашения, вы можете организовать, как вы привыкли в Java, включая создание соответствующих имен папок для пространств имен. Требования к папкам -> пакетам и файлам -> классам не существуют, так что вы можете именовать свои папки и классы независимо от пакетов и пространств имен.

Staale
источник
6

@ Мариус

Да, вы можете использовать несколько пространств имен одновременно, например:

using namespace boost;   
using namespace std;  

shared_ptr<int> p(new int(1));   // shared_ptr belongs to boost   
cout << "cout belongs to std::" << endl;   // cout and endl are in std

[Февраль 2014 - (Это действительно было так долго?): Этот конкретный пример сейчас неоднозначен, как Джои указывает ниже. Boost и std :: теперь у каждого есть shared_ptr.]

Адам Холлидж
источник
2
Обратите внимание, что теперь это stdтакже имеет shared_ptrместо, поэтому использование обоих boostи stdпространств имен будет конфликтовать, когда вы попытаетесь использовать a shared_ptr.
Джои
2
Это хороший пример того, почему многие производители программного обеспечения не рекомендуют импортировать целые пространства имен таким способом. Не вредно всегда указывать пространство имен, а если они слишком длинные, то создавать псевдоним или только важные конкретные классы из пространства имен.
Пэдди
5

Вы также можете содержать «using namespace ...» внутри функции, например:

void test(const std::string& s) {
    using namespace std;
    cout << s;
}
Shadow2531
источник
3

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

Адам Холлидж
источник
3

Я предпочитаю использовать пространство имен верхнего уровня для приложения и подпространства имен для компонентов.

То, как вы можете использовать классы из других пространств имен, на удивление очень похоже на то, как в Java. Вы можете использовать «use NAMESPACE», которое похоже на оператор «import PACKAGE», например, использовать std. Или вы указываете пакет как префикс класса, разделенного "::", например, std :: string. Это похоже на «java.lang.String» в Java.

dmeister
источник
3

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

Кристофер Джонсон
источник
2

Я использовал пространства имен C ++ так же, как и в C #, Perl и т. Д. Это просто семантическое разделение символов между стандартными библиотечными материалами, сторонними компонентами и моим собственным кодом. Я бы поместил свое собственное приложение в одно пространство имен, а затем повторно используемый библиотечный компонент в другом пространстве имен для разделения.

spoulson
источник
2

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

#include "lib/module1.h"
#include "lib/module2.h"

lib::class1 *v = new lib::class1();

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

KeithB
источник