Есть ли лучший способ выразить вложенные пространства имен в C ++ в заголовке

98

Я переключился с 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 #.

Если уникальность острых конфликтов составляет один балл (что также относится к C # и Java), хорошей идеей будет использовать имя проекта или название компании в качестве пространства имен, вам так не кажется?

С одной стороны, сказано, что C ++ - самый гибкий ... но все сказали: «Не делай этого»? Мне кажется, C ++ может многое делать, но во многих случаях у него ужасный синтаксис даже для самых простых вещей по сравнению с C #.

Обновление 2

Большинство пользователей считают бессмысленным создавать более глубокую вложенность, чем два уровня. Хорошо, а как насчет пространств имен Windows :: UI :: Xaml и Windows :: UI :: Xaml :: Controls :: Primitives в разработке под Win8? Я думаю, что использование пространств имен Microsoft имеет смысл и действительно глубже, чем просто 2 уровня. Я думаю, что более крупные библиотеки / проекты нуждаются в более глубоком вложении (я ненавижу такие имена классов, как ExtraLongClassNameBecauseEveryThingIsInTheSameNameSpace ... тогда вы также можете поместить все в глобальное пространство имен.)

Обновление 3 - Заключение

Большинство говорит «не делай этого», но ... даже boost имеет более глубокую вложенность, чем один или два уровня. Да, это библиотека, но: Если вам нужен код многократного использования - относитесь к своему собственному коду, как к библиотеке, которую вы отдадите кому-то другому. Я также использую более глубокое вложение для целей обнаружения с использованием пространств имен.

Пляжный ходок
источник
3
Это злоупотребление namespaceключевым словом?
Nawaz
4
пространства имен и системы модулей c # / java не служат одной и той же цели, поэтому вам не следует пытаться использовать их одинаково. и нет, нет более простого синтаксиса, просто потому, что нет смысла предоставлять синтаксис, чтобы упростить выполнение вещей, которые не предназначены для этого.
PlasmaHH 06
@PlasmaHH ... так что слабость - это отсутствие структурирования std lib в C ++? (см. мой простой пример в обновлении)
Beachwalker
@Stegi: Если вы можете привести веские аргументы, почему он отсутствует, и какие серьезные преимущества мы получили бы от такой структуризации, мы могли бы поговорить о потенциальных слабостях. До тех пор я бы назвал javas бесконечным вложением пакетов в лучшем случае запутанным.
PlasmaHH 09
3
@PlasmaHH Intellisense и другие помощники для / после включения заголовка (пакета). Для больших проектов в рамках одной компании может потребоваться несколько вложений (например, vw :: golflib :: io) для четкого определения того, что пространство имен содержит в какой «области». Что ж, вы могли бы просто использовать vw ::, но если пространство имен предназначено для избежания конфликтов, почему так ужасно объявлять? Это заканчивается тем, что никто не использует его или просто использует пространство имен с глубиной, равной единице (как часто предполагается).
Beachwalker

Ответы:

132

C ++ 17 может упростить определение вложенного пространства имен:

namespace A::B::C {
}

эквивалентно

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

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

W1M0R
источник
5
... включено переключателем компилятора/std:c++latest
солнечная луна
4
Обратите внимание: если вы будете использовать /std:c++latestVisual Studio 2015, а также Boost, вы можете столкнуться с очень мистическими ошибками компилятора при включении некоторых заголовков Boost. Я столкнулся с этой проблемой, как описано в этом вопросе StackOverflow
Вивит,
1
Он работает как есть, пространство имен A :: B :: C. Я тестировал с g ++ - 6.0
ervinbosenbacher
31

Чтобы избежать очень глубоких отступов, я обычно делаю это так:

namespace A { namespace B { namespace C
{
    class X
    {
        // ...
    };
}}}
Курт Хатчинсон
источник
3
JFYI clang-formatне может отформатировать это, поскольку вы показываете clang.llvm.org/docs/ClangFormatStyleOptions.html (NamespaceIndentation)
KindDragon
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;

Для вложения глубже трех уровней вам нужно будет добавить вспомогательные макросы до желаемого количества.

Макс Трукса
источник
Как #defineбы мне ни не нравилась эта магия препроцессора, меня очень впечатлила ... только если мне не нужно будет добавлять дополнительные вспомогательные макросы для более глубокого вложения ... ну, я все равно не собираюсь его использовать, так что .. .
Галдин
13

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

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

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

namespace nsc = a::b::c;

Но namespace nsc {}тогда это будет ошибкой, потому что пространство имен может быть определено только с использованием его имени- исходного-пространства-имен . По сути, стандарт упрощает задачу пользователя такой библиотеки, но затрудняет ее реализацию . Это отговаривает людей писать такие вещи, но смягчает последствия, если они это сделают.

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

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

Potatoswatter
источник
17
Хорошо, а как насчет пространств имен Windows :: UI :: Xaml и Windows :: UI :: Xaml :: Controls :: Primitives в разработке под Win8? Я думаю, что использование пространств имен Microsoft имеет смысл и действительно глубже, чем просто 2 уровня.
Beachwalker
2
Использование менее 2 уровней - это красный флаг, а использование 3 или 4 вполне нормально. Попытка достичь плоской иерархии пространств имен, когда это не имеет смысла, побеждает саму цель пространств имен - предотвращение конфликтов имен. Я согласен, что у вас должен быть один уровень для интерфейса, а другой - для субинтерфейсов и внутренних компонентов. Но вокруг этого вам понадобится как минимум еще один уровень для пространства имен компании (для малых и средних компаний) или два для компании и подразделения (для больших компаний). В противном случае пространства имен ваших интерфейсов будут конфликтовать с пространствами имен других интерфейсов с таким же именем, разработанными в другом месте
Kaiserludi
@Kaiserludi Какое техническое преимущество company::divisionнад company_division?
Potatoswatter
@Potatoswatter Внутри компании :: anotherDivsion вы можете просто использовать более короткое «деление». для ссылки на company :: Division даже внутри заголовков, где вы должны категорически избегать загрязнения пространств имен более высокого уровня использованием слова using namespace. Вне пространства имен компании вы все равно можете выполнить «using namespace company»; когда имена подразделений не конфликтуют с любыми другими пространствами имен в вашей области, но когда имена интерфейсов внутри некоторого пространства имен подразделений конфликтуют, так что вы не можете сделать «using namespace company_division;».
Kaiserludi
3
@Potatoswatter Дело в том, что вы получаете его практически бесплатно (company :: Division не длиннее, чем company_division), и вам не нужно сначала определять дополнительный псевдоним пространства имен, чтобы использовать его.
Kaiserludi
7

Нет, и, пожалуйста, не делай этого.

Назначение пространств имен в первую очередь - разрешение конфликтов в глобальном пространстве имен.

Второстепенное назначение - местное сокращение символов; например, сложный UpdateUIметод может использовать более using namespace WndUIкороткие символы.

Я работаю над проектом 1.3MLoc, и единственные пространства имен, которые у нас есть:

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

В этом проекте в именах классов и т. Д. Используется двух- или трехбуквенный «региональный» код (например, CDBNodeвместо DB::CNode). Если вы предпочитаете последнее, есть место для второго уровня «общедоступных» пространств имен, но не более того.

Перечисления для конкретных классов и т. Д. Могут быть членами этих классов (хотя я согласен, что это не всегда хорошо, и иногда трудно сказать, следует ли вам)

Пространство имен «компания» также редко требуется, за исключением случаев, когда у вас возникают большие проблемы со сторонними библиотеками, которые распространяются как двоичные, не предоставляют собственное пространство имен и не могут быть легко помещены в одно (например, в двоичном распространение). Тем не менее, по моему опыту, принудительно поместить их в пространство имен сделать гораздо проще.


[править] Согласно последующему вопросу Стеги:

Хорошо, а как насчет пространств имен Windows :: UI :: Xaml и Windows :: UI :: Xaml :: Controls :: Primitives в разработке под Win8? Я думаю, что использование пространств имен Microsoft имеет смысл и действительно глубже, чем просто 2 уровня.

Извините, если я недостаточно ясно понял: два уровня - это не жесткое ограничение, а большее - по сути не плохо. Я просто хотел указать, что вам редко нужно больше двух, по моему опыту, даже для большой базы кода. Более глубокое или более мелкое вложение - это компромисс.

Теперь, возможно, дело Microsoft обстоит иначе. Предположительно гораздо большая команда, и весь код - это библиотека.

Я предполагаю, что Microsoft имитирует здесь успех библиотеки .NET, где пространства имен способствуют обнаружению обширной библиотеки. (В .NET около 18000 типов.)

Далее я предполагаю, что в пространстве имен есть оптимальные (по порядку величины) символы. скажем, 1 не имеет смысла, 100 звучит правильно, 10000 явно слишком много.


TL; DR: Это компромисс, и у нас нет точных цифр. Играйте осторожно, не переусердствуйте в любом направлении. «Не делай этого» исходит просто из «У вас проблемы с этим, у меня будут проблемы с этим, и я не вижу причины, по которой вам это может понадобиться».

Петерхен
источник
8
Хорошо, а как насчет пространств имен Windows :: UI :: Xaml и Windows :: UI :: Xaml :: Controls :: Primitives в разработке под 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
5

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

Lzz распознает следующие конструкции C ++:

определение пространства имен

Безымянное пространство имен и все вложенные объявления выводятся в исходный файл. Это правило отменяет все остальные.

Имя именованного пространства имен может быть уточнено.

   namespace A::B { typedef int I; }

эквивалентно:

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

Конечно, качество источников, которое зависит от таких инструментов, является спорным ... Я бы сказал, что это скорее любопытство, показывающее, что синтаксическая болезнь, вызванная C ++, может принимать разные формы (у меня тоже есть ...)

CapelliC
источник
2

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

У меня сложилось впечатление, что не составляет большого труда разрешить размещение квалифицированного идентификатора помимо простого имени пространства имен, но по какой-то причине это запрещено.

Кирилл Кобелев
источник
2

В этом документе достаточно хорошо освещена тема: Документ о пространстве имен

Что в основном сводится к следующему. Чем длиннее ваши пространства имен, тем больше вероятность, что люди будут использовать 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;тогда пространство имен «testing» станет неоднозначным, и возникнут конфликты имен. Это означает, что ваша кодовая база может перейти от стабильной к нестабильной за счет включения сторонней библиотеки.

В C #, даже если вы должны использовать, using abc;и using def;компилятор может распознать это testing::myClassили даже просто myClassнаходится только в abc::testingпространстве имен, но C ++ не распознает это, и это обнаруживается как коллизия.

Сомнительный
источник
0

Да, тебе придется делать это как

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

Однако вы пытаетесь использовать пространства имен так, как они не должны использоваться. Проверьте этот вопрос, может быть, он вам пригодится.

SingerOfTheFall
источник
0

Вы можете использовать этот синтаксис:

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 с определениями вложенных пространств имен .

Удачного раскроя!

Источники:

smac89
источник
Это синтаксис, упомянутый в вопросе, где вместо этого ищется лучшее решение. Теперь с C ++ 17 доступна действительная альтернатива, как указано в принятом ответе. Извините, голос против, что не прочитал вопрос и ответ.
Beachwalker
@Beachwalker, давайте не будем зацикливаться на синтаксисе. Объявление пространства имен выше может быть таким же, как в принятом ответе. Главный момент, который я хотел выделить в этом ответе, - это то, что OP сказал, что он пропустил, и что я сделал под этим беспорядком в пространстве имен. Насколько я понимаю, все, похоже, были сосредоточены на объявлении всего в пространстве имен, тогда как мой ответ выводит вас из вложенного беспорядка, и я уверен, что OP оценил бы этот синтаксис, если бы кто-нибудь упомянул его 4 года назад, когда этот вопрос сначала спросили.
smac89 03
-1

[РЕДАКТИРОВАТЬ:]
Поскольку в 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, размещение изображения - не лучший вариант. Он избегает текстового поиска / индексации, а также затрудняет доступ к вашему ответу для посетителей с ослабленным зрением.
Генрих поддерживает Монику