Як правильно використовувати простори імен у C ++?


231

Я походжу з фону Java, де використовуються пакети, а не простори імен. Я звик складати класи, які працюють разом, щоб сформувати повний об'єкт у пакети, а потім повторно використовувати їх із цього пакету. Але зараз я працюю в C ++.

Як ви використовуєте простори імен у C ++? Чи створюєте єдиний простір імен для всієї програми чи створюєте простори імен для основних компонентів? Якщо так, як ви створюєте об'єкти з класів в інших просторах імен?

Відповіді:


167

Простори імен по суті є пакетами. Їх можна використовувати так:

namespace MyNamespace
{
  class MyClass
  {
  };
}

Потім у коді:

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

Або, якщо ви хочете завжди використовувати певний простір імен, можете зробити це:

using namespace MyNamespace;

MyClass* pClass = new MyClass();

Редагувати: Слідуючи тим, що Бернар як сказав , я, як правило, взагалі не використовую синтаксис "з використанням простору імен x", зазвичай явно вказую простір імен при створенні інстанцій моїх об'єктів (тобто перший показаний нами приклад).

І як ви просили нижче , ви можете використовувати скільки завгодно просторів імен.


25
ІМО краще просто звикнути до префіксації 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

Щоб не сказати все, Марк Інграм уже сказав невелику пораду щодо використання просторів імен:

Уникайте директиви "використання простору імен" у файлах заголовків - це відкриє простір імен для всіх частин програми, які імпортують цей файл заголовка. У файлах реалізації (* .cpp) це, як правило, не є великою проблемою - хоча я вважаю за краще використовувати директиву "з використанням простору імен" на рівні функцій.

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

Іноді простори імен використовуються у великих проектах C ++, щоб приховати деталі реалізації.

Додаткова примітка до директиви використання: Деякі люди вважають за краще використовувати "використання" лише для окремих елементів:

using std::cout;  
using std::endl;

2
Однією з переваг "використання простору імен" на рівні функцій, як ви пропонуєте, а не на рівні файлу .cpp або блоку простору імен {} блочного рівня в .cpp, є те, що це дуже допомагає при складанні одиничних компіляцій. "використання простору імен" є транзитивним і застосовується для простору імен A через дискретні блоки імен {A}} в одному і тому ж блоці, тому для побудови одиничної компіляції ви швидко використовуєте все, якщо вони виконані на рівні блоку файлів або простору імен.
idij

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;" відлякує Скотта Майєрса (я не пам'ятаю точно, яку книгу, але я можу знайти її в разі потреби).

Склад простору імен

Простору імен більше, ніж пакетів. Інший приклад можна знайти в книзі Bjarne Stroustrup "Мова програмування 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() ;
}

Ви навіть можете імпортувати вибрані символи з різних просторів імен, щоб створити власний інтерфейс простору імен. Мені ще потрібно знайти практичне використання цього, але теоретично це круто.


Чи можете ви уточнити, будь ласка, "надайте простір імен усьому коду у вашому модулі"? Що є доброю практикою для інкапсуляції в модуль. Наприклад, у мене є клас складних чисел і до зовнішніх функцій, пов'язаних зі складними числами. Цей клас і ці дві функції повинні бути в одному просторі імен?
yanpas

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 ++ розглядає функцію, що приймає тип як аргумент, як частину інтерфейсу для цього типу, тому не потрібно вказувати простір імен, оскільки параметр вже мав на увазі простору імен.

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

namespace ns {

class A
{
};

}

void print(A a)
{
}

І ваш код може почати викликати функцію print (a) куди завгодно. А тепер уявіть, що через кілька років автор вирішує надати функцію print (), кращу за вашу, тому що він знає внутрішні класи свого класу і може зробити кращу версію, ніж ваш.

Тоді автори C ++ вирішили, що його версію функції print () слід використовувати замість тієї, що надається в іншому просторі імен, для дотримання принципу інтерфейсу. І що це "оновлення" функції print () має бути максимально простим, це означає, що вам не доведеться змінювати кожен виклик функції print (). Ось чому "функції інтерфейсу" (функція в тому ж просторі імен, що і клас) можна викликати, не вказуючи простір імен у C ++.

І тому ви повинні розглядати простір імен C ++ як "інтерфейс", коли ви використовуєте його, і пам’ятайте про принцип інтерфейсу.

Якщо ви хочете краще пояснити цю поведінку, можете звернутися до книги « Винятковий С ++» від Herb Sutter


23
Ви дійсно повинні змінювати кожен дзвінок на print (), якщо додано ns :: Друк, але компілятор позначить кожен виклик як неоднозначний. Мовчазне переключення на нову функцію було б жахливою ідеєю.
Затемнення

Мені зараз цікаво, чи є те, що @Vincent сказав, що вам доведеться змінити всі дзвінки для друку, якщо автор надасть функцію ns :: Print (), що ви намагалися сказати? Що коли автор додав функцію ns :: Print (), ви можете просто видалити власну реалізацію? Або що ви просто додасте, використовуючи ns :: print (), використовуючи-декларацію? Або щось інше? Спасибі
Васька ель ґато

36

Більші проекти C ++, які я бачив, навряд чи використовували більше одного простору імен (наприклад, збільшити бібліотеку).

Фактично 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(тобто ви можете визначити одне простору імен для кількох файлів).


11

На Java:

package somepackage;
class SomeClass {}

В C ++:

namespace somenamespace {
    class SomeClass {}
}

І використовуючи їх, Java:

import somepackage;

І C ++:

using namespace somenamespace;

Також повні імена - це "somepackge.SomeClass" для Java та "пространство прозвища": SomeClass "для C ++. За допомогою цих конвенцій ви можете організувати, як ви звикли в Java, включаючи створення відповідних імен папок для просторів імен. Однак, вимог до папки та пакета та файлів немає, тож ви можете назвати папки та класи незалежно від пакетів та просторів імен.


6

@ marius

Так, ви можете одночасно використовувати кілька просторів імен, наприклад:

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 :: тепер у кожного є спільний_птр.]


2
Зауважте, що це stdтакож є shared_ptrна даний момент, тому використання обох boostі stdпросторів імен зіткнеться при спробі використання shared_ptr.
Joey

2
Це хороший приклад того, чому багато програмних будинків не дозволять імпортувати цілі простори імен таким чином. Не завадить завжди вказувати простір імен, і якщо вони занадто довгі, то роблять псевдонім або лише важливі конкретні класи з простору імен.
Падді

5

Ви також можете містити "використання простору імен ..." всередині функції, наприклад:

void test(const std::string& s) {
    using namespace std;
    cout << s;
}

3

Взагалі кажучи, я створюю простір імен для коду, якщо я вважаю, що можливо, функція чи тип імен конфліктують з іншими бібліотеками. Це також допомагає створити код бренду, ala boost :: .


3

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

Те, як можна використовувати класи з інших просторів імен, напрочуд дуже схоже на те, як у Java. Ви можете або використовувати "use NAMESPACE", подібний до заяви "імпортувати PACKAGE", наприклад, використовувати std. Або ви вказуєте пакунок як префікс класу, розділеного "::", наприклад, std :: string. Це схоже на "java.lang.String" на Java.


3

Зауважте, що простір імен на C ++ насправді є простором імен. Вони не надають жодної інкапсуляції, яку роблять пакети на Java, тому ви, ймовірно, не будете користуватися ними так сильно.


2

Я використовував простори імен C ++ так само, як і в C #, Perl тощо. Це просто семантичне розділення символів між стандартними бібліотечними речами, сторонніми матеріалами та власним кодом. Я розміщую власний додаток в одному просторі імен, потім компонент бібліотеки для багаторазового використання в іншому просторі імен для розділення.


2

Ще одна відмінність java від C ++ полягає в тому, що в C ++ ієрархії простору імен не потрібно обробляти макет файлової системи. Тому я схильний розміщувати цілу бібліотеку для багаторазового використання в одному просторі імен, а підсистеми всередині бібліотеки - в підкаталогах:

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

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

Я б розміщував підсистеми у вкладених просторах імен, лише якщо була б можливість конфлікту імен.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.