Чи є кращий спосіб виразити вкладені простори імен у C ++ всередині заголовка


97

Я перейшов з C ++ на Java та C # і думаю, що використання просторів імен / пакетів там набагато краще (добре структуровано). Потім я повернувся до C ++ і спробував використовувати простори імен так само, але необхідний синтаксис жахливий у заголовковому файлі.

namespace MyCompany
{
    namespace MyModule
    {
        namespace MyModulePart //e.g. Input
        {
            namespace MySubModulePart
            {
                namespace ...
                {
                    public class MyClass    

Мені теж здається дивним (щоб уникнути глибокого відступу):

namespace MyCompany
{
namespace MyModule
{
namespace MyModulePart //e.g. Input
{
namespace MySubModulePart
{
namespace ...
{
     public class MyClass
     {

Чи існує коротший спосіб висловити вищезазначене? Мені чогось не вистачає

namespace MyCompany::MyModule::MyModulePart::...
{
   public class MyClass

Оновлення

Гаразд, деякі кажуть, що концепція використання в Java / C # та C ++ відрізняється. Дійсно? Я думаю, що (динамічне) завантаження класів не є єдиною метою просторів імен (це дуже технічно аргументована перспектива). Чому б мені не використовувати його для читабельності та структурування, наприклад, думати про "IntelliSense".

В даний час немає логіки / клею між простором імен і тим, що ви можете там знайти. Java та C # роблять це набагато краще ... Навіщо включати <iostream>та мати простір імен std? Гаразд, якщо ви говорите, що логіка повинна покладатися на заголовок, чому #include не використовує дружній синтаксис "IntelliSense", як #include <std::io::stream>або <std/io/stream>? Я думаю, що відсутність структурування у бібліотеках за замовчуванням є однією з слабких сторін C ++ порівняно з Java / C #.

Якщо унікальність для завзятих конфліктів - це одна Точка (а це також точка С # та Java), хороша ідея - використовувати назву проекту або назву компанії як простір імен, чи не так Ви вважаєте?

З одного боку, кажуть, що C ++ є найбільш гнучким ... але всі сказали "не робити цього"? Мені здається, що C ++ може робити багато речей, але має жахливий синтаксис навіть у найпростіших справах у багатьох випадках порівняно з C #.

Оновлення 2

Більшість користувачів заявляє, що створювати глибше вкладання, ніж два рівні, - це нісенітниця. Гаразд, то як щодо Windows :: UI :: Xaml та Windows :: UI :: Xaml :: Controls :: Простіри імен примітивів у розробці Win8? Я думаю, що використання простором імен Microsoft має сенс, і це насправді глибше, ніж просто 2 рівні. Я думаю, що більші бібліотеки / проекти потребують більш глибокого вкладання (я ненавиджу імена класів, такі як ExtraLongClassNameBecauseEveryThingIsInTheSameNameSpace ... тоді ви також можете розмістити все у глобальному просторі імен.)

Оновлення 3 - Висновок

Більшість кажуть "не робіть цього", але ... навіть підсилення має більш глибоке вкладання, ніж один-два рівні. Так, це бібліотека, але: якщо ви хочете використовувати код багаторазового використання - поводьтесь із власним кодом як з бібліотекою, яку ви надасте комусь іншому. Я також використовую більш глибоке вкладання для цілей виявлення, використовуючи простори імен.


3
Це зловживання namespaceключовим словом?
Nawaz

4
простори імен та системи модулів c # / java не мають однакових цілей, тому не слід намагатися використовувати їх однаково. і ні, простішого синтаксису не існує, просто тому, що немає сенсу надавати синтаксис, щоб полегшити речі, які не призначені для виконання.
PlasmaHH

@PlasmaHH ... отже, слабкою стороною є відсутність структуризації std-бібліотеки C ++? (див. мій простий приклад в оновленні)
Beachwalker

@Stegi: Якщо ви можете навести вагомі аргументи, чому цього не вистачає, і які суттєві вигоди ми отримали б від такої структуризації, ми могли б поговорити про потенційні слабкі місця. До тих пір я б називав javas нескінченним вкладанням пакетів, що в кращому випадку заплутує.
PlasmaHH

3
@PlasmaHH Intellisense та інші помічники для / після включення заголовка (пакета). Для великих проектів у межах однієї компанії може знадобитися більше одного вкладеного (наприклад, vw :: golflib :: io) для чіткого викладу, що містить простір імен під яким "обсягом". Ну, ви можете просто використати vw ::, але якщо простір імен призначений для уникнення зіткнень, чому це так жахливо заявляти? Це закінчується до того моменту, що ніхто не використовує його або просто використовує простір імен із глибиною до одного (як часто пропонують).
Beachwalker

Відповіді:


130

C ++ 17 може спростити визначення вкладеного простору імен:

namespace A::B::C {
}

еквівалентно

namespace A { namespace B { namespace C {
} } }

Див. (8) на сторінці простору імен на cppreference:
http://en.cppreference.com/w/cpp/language/namespace


5
... увімкнено перемикачем компілятора/std:c++latest
сонячний місяць

3
Зауважте, що якщо ви будете використовувати /std:c++latestVisual Studio 2015, а також використовувати Boost, ви можете зіткнутися з дуже містичними помилками компілятора, включивши деякі заголовки Boost. Я зіткнувся з цією проблемою, як описано в цьому питанні StackOverflow
Вівіт,

1
Він працює як є, простір імен A :: B :: C. Я тестував з g ++ - 6.0
ervinbosenbacher


17

Я повністю підтримую відповідь Петерхена, але хочу додати щось, що стосується іншої частини вашого запитання.

Оголошення просторів імен є одним з дуже рідкісних випадків у C ++, коли мені насправді подобається використання #defines.

#define MY_COMPANY_BEGIN  namespace MyCompany { // begin of the MyCompany namespace
#define MY_COMPANY_END    }                     // end of the MyCompany namespace
#define MY_LIBRARY_BEGIN  namespace MyLibrary { // begin of the MyLibrary namespace
#define MY_LIBRARY_END    }                     // end of the MyLibrary namespace

Це також позбавляє потреби в коментарях біля закриваючої фігурної дужки простору імен (Ви коли-небудь прокручували вниз до нижньої частини великого вихідного файлу та намагалися додати / видалити / збалансувати фігурні дужки, у яких бракувало коментарів про те, яка фігурна дужка закриває яку область? Не цікаво .).

MY_COMPANY_BEGIN
MY_LIBRARY_BEGIN

class X { };

class Y { };

MY_LIBRARY_END
MY_COMPANY_END

Якщо ви хочете помістити всі оголошення простору імен в один рядок, ви можете зробити це, а також трохи (досить потворно) магії препроцесора:

// helper macros for variadic macro overloading
#define VA_HELPER_EXPAND(_X)                    _X  // workaround for Visual Studio
#define VA_COUNT_HELPER(_1, _2, _3, _4, _5, _6, _Count, ...) _Count
#define VA_COUNT(...)                           VA_HELPER_EXPAND(VA_COUNT_HELPER(__VA_ARGS__, 6, 5, 4, 3, 2, 1))
#define VA_SELECT_CAT(_Name, _Count, ...)       VA_HELPER_EXPAND(_Name##_Count(__VA_ARGS__))
#define VA_SELECT_HELPER(_Name, _Count, ...)    VA_SELECT_CAT(_Name, _Count, __VA_ARGS__)
#define VA_SELECT(_Name, ...)                   VA_SELECT_HELPER(_Name, VA_COUNT(__VA_ARGS__), __VA_ARGS__)

// overloads for NAMESPACE_BEGIN
#define NAMESPACE_BEGIN_HELPER1(_Ns1)             namespace _Ns1 {
#define NAMESPACE_BEGIN_HELPER2(_Ns1, _Ns2)       namespace _Ns1 { NAMESPACE_BEGIN_HELPER1(_Ns2)
#define NAMESPACE_BEGIN_HELPER3(_Ns1, _Ns2, _Ns3) namespace _Ns1 { NAMESPACE_BEGIN_HELPER2(_Ns2, _Ns3)

// overloads for NAMESPACE_END
#define NAMESPACE_END_HELPER1(_Ns1)               }
#define NAMESPACE_END_HELPER2(_Ns1, _Ns2)         } NAMESPACE_END_HELPER1(_Ns2)
#define NAMESPACE_END_HELPER3(_Ns1, _Ns2, _Ns3)   } NAMESPACE_END_HELPER2(_Ns2, _Ns3)

// final macros
#define NAMESPACE_BEGIN(_Namespace, ...)    VA_SELECT(NAMESPACE_BEGIN_HELPER, _Namespace, __VA_ARGS__)
#define NAMESPACE_END(_Namespace, ...)      VA_SELECT(NAMESPACE_END_HELPER,   _Namespace, __VA_ARGS__)

Тепер ви можете зробити це:

NAMESPACE_BEGIN(Foo, Bar, Baz)

class X { };

NAMESPACE_END(Baz, Bar, Foo) // order doesn't matter, NAMESPACE_END(a, b, c) would work equally well

Foo::Bar::Baz::X x;

Для вкладеності глибше трьох рівнів вам потрібно було б додати допоміжні макроси до бажаної кількості.


Скільки мені не подобається #defines, мене цілком вражає ця магія препроцесора ... лише якщо мені не потрібно додавати додаткові допоміжні макроси для глибшого вкладання ... ну, я все одно не буду його використовувати, так .. .
Галдіна

12

Простори імен С ++ використовуються для групування інтерфейсів, а не для розділення компонентів або вираження політичного поділу.

Стандарт намагається заборонити використання просторів імен, подібних до Java. Наприклад, псевдоніми простору імен надають спосіб легко використовувати глибоко вкладені або довгі імена простору імен.

namespace a {
namespace b {
namespace c {}
}
}

namespace nsc = a::b::c;

Але namespace nsc {}тоді це буде помилкою, оскільки простір імен може бути визначений лише за допомогою його оригінального-імені простору імен . По суті, стандарт робить речі простими для користувача такої бібліотеки, але важкими для реалізатора . Це відбиває людей писати подібні речі, але пом'якшує наслідки, якщо вони це роблять.

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

Розгляньте можливість використання символів підкреслення та префіксів ідентифікаторів там, де ::оператор не потрібен.


17
Гаразд, то як щодо Windows :: UI :: Xaml та Windows :: UI :: Xaml :: Controls :: Простіри імен примітивів у розробці Win8? Я думаю, що використання просторами імен Microsoft має сенс, і це насправді глибше, ніж просто 2 рівні.
Beachwalker

2
Використовувати менше 2 рівнів - це червоний прапор, а використання 3 або 4 - цілком нормально. Спроба досягти плоскої ієрархії простору імен, коли це не має сенсу, перемагає саму мету просторів імен - уникаючи зіткнень імен. Я погоджуюсь, що у вас повинен бути один рівень для інтерфейсу, а інший - для підінтерфейсів та внутрішніх елементів. Але навколо цього вам потрібен принаймні ще один рівень для простору імен компаній (для малих та середніх компаній) або два для компаній та підрозділів (для великих компаній). В іншому випадку ваші простори імен інтерфейсу зіткнуться з інтерфейсами інших інтерфейсів з такою ж назвою, розроблених в інших місцях
Kaiserludi,

@Kaiserludi Що таке технічна перевага company::divisionза кадром company_division?
Potatoswatter

@Potatoswatter Усередині компанії :: anotherDivsion ви можете просто скористатися коротшим `` розділенням ''. посилатися на company :: division навіть у заголовках, де слід категорично уникати забруднення просторів імен вищого рівня за допомогою використання 'простору імен'. Поза простором імен компанії ви все ще можете зробити `` за допомогою простору імен компанії; '' коли імена підрозділів не стикаються з будь-якими іншими просторами імен у вашій області дії, але коли імена інтерфейсів всередині деякого простору імен підрозділів стикаються, так що ви не можете зробити 'за допомогою простору імен company_division;'.
Кайзерлуді

3
@Potatoswatter Справа в тому, що ви отримуєте його практично безкоштовно (company :: Division не довше, ніж company_division) і не потрібно спочатку визначати додатковий псевдонім простору імен, щоб використовувати його.
Кайзерлуді,

6

Ні, і будь ласка, не роби цього.

Призначення просторів імен полягає насамперед у вирішенні конфліктів у глобальному просторі імен.

Другорядною метою є місцеве скорочення символів; наприклад, комплексUpdateUI метод може використовувати a using namespace WndUIдля використання коротших символів.

Я працюю над проектом 1.3MLoc, і єдиними просторами імен, які ми маємо, є:

  • імпортовані зовнішні бібліотеки COM (головним чином, щоб ізолювати конфлікти заголовків між #import та #include windows.h)
  • Один рівень просторів імен "відкритого API" для певних аспектів (інтерфейс, доступ до БД тощо)
  • Простори імен "Деталізація реалізації", які не є частиною загальнодоступного API (анонімні простори імен у .cpp, або ModuleDetailHereBeTygersпростори імен у бібліотеках лише для заголовків)
  • Переліки - це найбільша проблема на моєму досвіді. Вони забруднюють, як божевільні.
  • Я все ще відчуваю, що це занадто багато просторів імен

У цьому проекті назви класів тощо використовують дво- або трибуквенний код "регіону" (наприклад, CDBNodeзамість DB::CNode). Якщо ви віддаєте перевагу останньому, є місце для другого рівня "публічних" просторів імен, але не більше.

До цих класів можуть входити переліки для класів тощо (хоча я згоден, що це не завжди добре, і іноді важко сказати, чи варто це робити)

Також рідко потрібен простір імен "компанія", за винятком випадків, коли у вас є великі проблеми зі сторонніми бібліотеками, які розподіляються у вигляді двійкових файлів, не надають власний простір імен, і їх неможливо легко вкласти в один (наприклад, у двійковій системі) розподіл). Тим не менше, на моєму досвіді примусити їх до простору імен зробити набагато простіше.


[редагувати] Відповідно до подальшого запитання Стегі:

Гаразд, то як щодо Windows :: UI :: Xaml та Windows :: UI :: Xaml :: Controls :: Простіри імен примітивів у розробці Win8? Я думаю, що використання простором імен Microsoft має сенс, і це насправді глибше, ніж просто 2 рівні

Вибачте, якщо я не був достатньо чітким: два рівні - це не жорсткий межа, і більше - це не суттєво погано. Я просто хотів зазначити, що вам рідко потрібно більше двох, з мого досвіду, навіть на великій основі коду. Вкладання глибше або неглибоко - це компроміс.

Зараз випадок з Microsoft може бути іншим. Імовірно, набагато більша команда, і весь код - це бібліотека.

Я припускаю, що Microsoft наслідує тут успіх бібліотеки .NET, де простори імен сприяють виявленню великої бібліотеки. (.NET має близько 18000 типів.)

Далі я б припустив, що в просторі імен є оптимальні (порядок) символи. скажімо, 1 не має сенсу, 100 звучить правильно, 10000 - це явно багато.


TL; DR: Це компроміс, і у нас немає твердих цифр. Грайте безпечно, не перестарайтеся в будь-якому напрямку. "Не робіть цього" походить просто від "У вас з цим проблеми, у мене будуть проблеми з цим, і я не бачу причини, чому б вам це було потрібно".


8
Гаразд, то як щодо Windows :: UI :: Xaml та Windows :: UI :: Xaml :: Controls :: Простіри імен примітивів у розробці Win8? Я думаю, що використання просторами імен Microsoft має сенс, і це насправді глибше, ніж просто 2 рівні.
Beachwalker

2
Якщо мені потрібні константи глобального доступу, я хотів би помістити їх у простір імен з таким ім’ям, як Constantsпотім, а потім створити вкладені простори імен з відповідними іменами для класифікації констант; якщо потрібно, тоді я використовую додаткові простори імен, щоб запобігти зіткненню імен. ConstantsСам цей простір імен міститься у загальноприйнятому просторі імен системного коду програми з таким іменем, як SysData. Це створює повне ім'я , що містить три або чотири простору імен (наприклад, SysData::Constants::ErrorMessages, SysData::Constants::Ailments::Bitflagsабо SysData::Defaults::Engine::TextSystem).
Час Джастіна - поновити Моніку

1
Коли константи потрібні у фактичному коді, будь-яка потрібна їм функція використовує usingдирективу для введення відповідних імен, мінімізуючи можливість конфліктуючих імен. Я вважаю, що це покращує читабельність і допомагає документувати залежності будь-якого даного коду. Окрім констант, я намагаюся, якщо це можливо, зберігати його у двох просторах імен (таких як SysData::Exceptionsі SysData::Classes).
Час Джастіна - поновити Моніку

2
В цілому, я б сказав, що в загальних випадках найкраще використовувати мінімальну кількість вкладених просторів імен, але якщо вам потрібні глобальні об'єкти з якихось причин (константні чи змінні, бажано перші), слід використовувати кілька вкладених просторів імен. розділити їх на відповідні категорії, як для документування їх використання, так і для мінімізації потенційних колізій імен.
Час Джастіна - поновити Моніку

2
-1 за "будь ласка, не робіть цього" без об'єктивних причин (незважаючи на роз'яснення пізніше). Мова підтримує вкладені простори імен, і проект може мати вагомі причини для їх використання. Обговорення можливих таких причин та будь-яких конкретних, об'єктивних недоліків, які б зробили це, перевернуло б моє проти.
TypeIA

4

Ось цитата з документів Lzz (Lazy C ++):

Lzz розпізнає такі конструкції C ++:

визначення простору імен

Неіменований простір імен та всі вкладені декларації виводяться у вихідний файл. Це правило має перевагу над усіма іншими.

Ім'я названого простору імен може бути кваліфікованим.

   namespace A::B { typedef int I; }

еквівалентно:

   namespace A { namespace B { typedef int I; } }

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


2

Обидва стандарти (C ++ 2003 та C ++ 11) дуже чітко визначають, що ім'я простору імен є ідентифікатором. Це означає, що потрібні явно вкладені заголовки.

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


1

Ви можете використовувати цей синтаксис:

namespace MyCompany {
  namespace MyModule {
    namespace MyModulePart //e.g. Input {
      namespace MySubModulePart {
        namespace ... {
          class MyClass;
        }
      }
    }
  }
}

// Here is where the magic happens
class MyCompany::MyModule::MyModulePart::MySubModulePart::MyYouGetTheIdeaModule::MyClass {
    ...
};

Зверніть увагу, що цей синтаксис дійсний навіть у C ++ 98, і він майже подібний до того, що зараз доступний у C ++ 17 із вкладеними визначеннями простору імен .

Щасливого розкладання грошей!

Джерела:


Це ситаксис, згаданий у питанні, де замість цього шукають кращого рішення. Тепер із C ++ 17 є дійсна альтернатива, як зазначено у прийнятій відповіді. Вибачте, проголосуйте за те, що не прочитали питання та відповідь.
Beachwalker

@Beachwalker давайте не будемо захоплюватися синтаксисом. Оголошення простору імен вище також може бути таким самим, як і у прийнятій відповіді. Основним моментом, на який я хотів би підкреслити цією відповіддю, є те, що, як сказав ОП, він пропустив, і те, що я зробив під цим безладом у просторі імен. Наскільки я бачу, всі, здається, були зосереджені на декларуванні всього в просторі імен, тоді як моя відповідь виводить вас із вкладеного безладу, і я впевнений, що OP оцінив би цей синтаксис, якби хтось згадав про це 4 роки тому, коли це питання спочатку запитали.
smac89

1

Ця стаття досить добре висвітлює тему: Папір простору імен

Що в основному це зводить. Чим довші ваші простори імен, тим більша ймовірність того, що люди збираються використовувати using namespaceдирективу.

Отже, дивлячись на наступний код, ви можете побачити приклад, коли це вам зашкодить:

namespace abc { namespace testing {
    class myClass {};
}}

namespace def { namespace testing {
    class defClass { };
}}

using namespace abc;
//using namespace def;

int main(int, char**) {
    testing::myClass classInit{};
}

Цей код буде скомпільовано чудово, однак, якщо ви прокоментуєте рядок, //using namespace def;тоді простір імен "тестування" стане неоднозначним, і у вас виникнуть колізії імен. Це означає, що ваша кодова база може перейти від стабільної до нестабільної, включивши сторонню бібліотеку.

У C #, навіть якщо ви мали використовувати, using abc;і using def;компілятор може розпізнати це testing::myClassабо навіть просто myClassзнаходиться лише у abc::testingпросторі імен, але C ++ не розпізнає це, і це виявляється як зіткнення.


0

Так, вам доведеться робити це як

namespace A{ 
namespace B{
namespace C{} 
} 
}

Однак ви намагаєтесь використовувати простори імен таким чином, щоб вони не використовувались. Перевірте це питання, можливо, вам це стане в нагоді.


-1

[EDIT:]
Оскільки c ++ 17 вкладені простори імен підтримуються як стандартна мовна функція ( https://en.wikipedia.org/wiki/C%2B%2B17 ). На сьогодні ця функція не підтримується в g ++ 8, але її можна знайти у компіляторі clang ++ 6.0.


[ВИСНОВОК:]
Використовуйте clang++6.0 -std=c++17як команду компіляції за замовчуванням. Тоді все повинно працювати нормально - і ви зможете компілюватиnamespace OuterNS::InnerNS1::InnerNS2 { ... } файлів.


[ОРИГІНАЛЬНА ВІДПОВІДЬ:]
Оскільки це питання вже застаріле, я припускаю, що Ви пішли далі. Але для інших, хто все ще шукає відповіді, я придумав таку ідею:

Буфери Emacs, що відображають головний файл, файли простору імен, команду / результат компіляції та виконання командного рядка.

(Чи можу я зробити тут рекламу для Emacs :)?) Опублікувати зображення набагато простіше і легше для читання, ніж просто поштовий індекс. Я не маю наміру подавати повноцінний огляд усіх кутових випадків, просто я мав намір надихнутись. (Я повністю підтримую C # і вважаю, що в багатьох випадках C ++ повинен використовувати деякі функції ООП, оскільки C # популярний головним чином завдяки порівнянній простоті використання).


Оновлення: Оскільки C ++ 17 має вкладений простір імен ( nuonsoft.com/blog/2017/08/01/c17-nested-namespaces ), здається, моя відповідь більше не актуальна, якщо ви не використовуєте старіші версії C ++ .
ワ イ き ん ぐ

1
Наскільки я люблю Emacs, розміщення зображення не є ідеальним. Це ухиляється від пошуку / індексування тексту, а також ускладнює доступ до вашої відповіді для відвідувачів із вадами зору.
Генріх підтримує Моніку
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.