是否有更好的方法在标头中以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>or <std/io/stream>?我认为默认库中缺少的结构化是C ++与Java / C#相比的弱点之一。

如果对激烈冲突的唯一性是一个要点(这也是C#和Java的要点),那么一个好主意是使用项目名称或公司名称作为名称空间,您不这样认为吗?

一方面,有人说C ++是最灵活的……但是每个人都说“不要这样做”吗?在我看来,C ++可以做很多事情,但是与C#相比,即使在许多情况下最简单的事情,语法也很糟糕。

更新2

大多数用户说创建比两个级别更深的嵌套是胡说八道。好的,那么Win8开发中的Windows :: UI :: Xaml和Windows :: UI :: Xaml :: Controls :: Primitives命名空间呢?我认为Microsoft对名称空间的使用是有道理的,而且确实比2个层次更深入。我认为更大的库/项目需要更深层的嵌套(我讨厌ExtraLongClassNameBecauseEveryThingIsInTheSameNameSpace之类的类名,因此也可以将所有内容都放入全局名称空间中。)

更新3-结论

多数人说“不做”,但是...甚至增强的嵌套也比一两个层次更深。是的,它是一个库,但是:如果您想要可重用的代码,请像对待库一样对待自己的代码,然后将其交给其他人。我还使用更深层的嵌套来使用命名空间进行发现。


3
滥用namespace关键字吗?
纳瓦兹

4
命名空间和c#/ java模块系统的作用不同,因此,您不应尝试以相同的方式使用它们。不,没有更简单的语法,只是因为提供一种使事情更容易做而不是要做的语法没有意义。
PlasmaHH 2012年

@PlasmaHH ...所以弱点是C ++的std lib缺少结构化吗?(请参阅更新中的简单示例)
Beachwalker

@Stegi:如果您可以提出合理的论据,说明为什么缺少它,以及从这种结构化中我们将获得什么切实的好处,我们可以谈谈潜在的弱点。在那之前,我称javas包的无休止的嵌套充其量是令人困惑的。
PlasmaHH 2012年

3
@PlasmaHH Intellisense和其他用于头文件(包)之后的帮助器。一家公司内的大型项目可能需要多个嵌套(例如vw :: golflib :: io),才能清楚地声明名称空间在哪个“作用域”中包含什么。好吧,您可以只使用vw :::但如果要使用命名空间来避免冲突,那么为什么这么恐怖地声明呢?最终导致没有人使用它或仅使用深度为1的名称空间(通常是提示)。
Beachwalker

Answers:


130

C ++ 17可能简化嵌套名称空间的定义:

namespace A::B::C {
}

相当于

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

请参阅cppreference的名称空间页面上的(8):http :
//en.cppreference.com/w/cpp/language/namespace


5
...由编译器开关启用/std:c++latest
2016年

3
请注意,如果您将/std:c++latest在Visual Studio 2015中使用并使用Boost,则在包含一些Boost标头时可能会遇到非常神秘的编译器错误。我遇到了这个StackOverflow
Vivit

1
它按名称空间A :: B :: C的样子工作。我已经使用g ++-6.0进行了测试
ervinbosenbacher


17

我完全支持peterchen的回答,但想添加一些解决您问题另一部分的内容。

声明名称空间是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,但这种预处理器的魔力给我留下了深刻的印象……仅当我不必为深层嵌套而添加其他辅助宏时……好吧,我还是不会使用它。 。
galdin

12

C ++名称空间用于对接口进行分组,而不是用于划分组件或表达政治上的分歧。

该标准竭力禁止类似Java的名称空间使用。例如,名称空间别名提供了一种轻松使用深度嵌套或较长名称空间名称的方法。

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

namespace nsc = a::b::c;

但这namespace nsc {}将是一个错误,因为只能使用其original-namespace-name定义名称空间。本质上,该标准使此类库的用户易于使用,但对实施者而言则很困难。这使人们不愿意写这类东西,但是如果这样做的话,则会减轻影响。

每个接口应该由一组相关的类和函数定义一个名称空间。内部或可选子接口可能会进入嵌套名称空间。但是超过两个级别的深度应该是一个非常严重的危险信号。

考虑在::不需要运算符的地方使用下划线字符和标识符前缀。


17
好的,那么Win8开发中的Windows :: UI :: Xaml和Windows :: UI :: Xaml :: Controls :: Primitives命名空间呢?我认为Microsoft对名称空间的使用是有道理的,而且确实比2个层次更深入。
Beachwalker

2
使用少于2个级别是一个危险信号,而使用3或4个完全可以。试图在没有意义的情况下实现平坦的名称空间层次结构会破坏名称空间的真正目的-避免名称冲突。我同意您应该为一个接口设置一个级别,为子接口和内部设置一个级别。但是围绕这一点,您至少需要为公司名称空间(对于中小型公司)增加至少一层,或者为公司和部门(对于大公司)增加至少一层。否则,您的接口名称空间将与其他位置使用其他名称的其他接口的名称空间发生冲突
Kaiserludi,

@Kaiserludi什么是技术优势company::division超过company_division
Potatoswatter

@Potatoswatter公司内部:: anotherDivsion,您可以只使用较短的“部门”。甚至在标头中也要引用company :: division,在标头中您应强烈避免通过使用“使用名称空间”来污染更高级别的名称空间。在公司名称空间之外,您仍然可以执行“使用名称空间公司”;当部门名称不与您的范围内的任何其他名称空间冲突时,但是当某些部门名称空间内的接口名称发生冲突时,您就不能“使用名称空间company_division;”。
Kaiserludi

3
@Potatoswatter关键是您实际上可以免费获得它(company :: division的时间不比company_division更长),并且不必先定义额外的名称空间别名即可使用它。
Kaiserludi

6

不,请不要那样做。

命名空间的目的主要是解决全局命名空间中的冲突。

第二个目的是符号的局部缩写。例如,复杂的UpdateUI方法可以使用using namespace WndUI来使用较短的符号。

我在一个1.3MLoc项目上,我们仅有的命名空间是:

  • 导入外部COM库(主要是为了隔离#import和之间的标头冲突 #include windows.h
  • 某些方面(UI,数据库访问等)的一层“公共API”名称空间
  • 不属于公共API的“实现细节”名称空间(.cpp中的匿名名称空间,或ModuleDetailHereBeTygers仅标头库中的名称空间)
  • 枚举是我经验中最大的问题。他们像疯子一样污染。
  • 我仍然觉得它完全是太多的名称空间

在此项目中,类名等使用两个或三个字母的“区域”代码(例如CDBNode代替DB::CNode)。如果您更喜欢后者,那么还有第二层“公共”名称空间的空间,但仅此而已。

特定于类的枚举等可以成为这些类的成员(尽管我同意这并不总是一件好事,有时很难说是否应该这样做)

也很少需要“公司”名称空间,除非您对以二进制形式分发的第三方库有很大的疑问,不要提供自己的名称空间,并且不能轻易地将其放入一个(例如,以二进制形式)分配)。不过,以我的经验,将它们强制为命名空间要容易得多。


[编辑]根据Stegi的后续问题:

好的,那么Win8开发中的Windows :: UI :: Xaml和Windows :: UI :: Xaml :: Controls :: Primitives命名空间呢?我认为Microsoft对名称空间的使用是有道理的,而且确实比2个层次更深入

很抱歉,如果我不够清楚:两个级别不是硬性限制,更多级别本质上不是坏的。我只想指出,您很少需要,就我的经验而言,即使是在大型代码库上两个以上。嵌套更深或更浅是一个权衡。

现在,微软的情况可以说是不同的。大概是一个更大的团队,所有代码都是库。

我假设微软在这里是在模仿.NET库的成功,其中命名空间有助于扩展库的可发现性。(.NET有大约18000种类型。)

我进一步假设在名称空间中存在一个最佳符号(数量级)。例如,1没有意义,100声音正确,10000显然很多。


TL; DR:这是一个折衷,我们没有确切的数字。安全起见,请勿在任何方向上过度操作。“不这样做”仅来自“您对此有问题,我对此有问题,但我看不出您为什么需要它的原因。”。


8
好的,那么Win8开发中的Windows :: UI :: Xaml和Windows :: UI :: Xaml :: Controls :: Primitives命名空间呢?我认为Microsoft对名称空间的使用是有道理的,而且确实比2个层次更深入。
Beachwalker

2
如果我需要全局访问常量,则希望将它们放在名称为的名称空间中Constants,然后创建具有适当名称的嵌套名称空间以对常量进行分类;如有必要,然后使用其他名称空间来防止名称冲突。该Constants名称空间本身包含在程序的系统代码的全部名称空间中,名称为SysData。这产生含有三个或四个命名空间一个完全合格的名称(例如SysData::Constants::ErrorMessagesSysData::Constants::Ailments::BitflagsSysData::Defaults::Engine::TextSystem)。
贾斯汀时间-恢复莫妮卡

1
当实际代码中需要常量时,需要它们的任何函数都将使用using指令来引入适当的名称,以最大程度地减少名称冲突的可能性。我发现它可以提高可读性,并有助于记录任何给定代码块的依赖性。除了常量之外,如果可能的话,我倾向于尝试将其保留在两个名称空间中(例如SysData::ExceptionsSysData::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所说的他错过了什么,以及我在该名称空间混乱之下所做的事情。据我所知,似乎每个人似乎都专注于声明名称空间中的所有内容,而我的回答却使您摆脱了嵌套的混乱局面,并且我相信OP会赞赏4年前有人问到这个语法的问题。首先被问到。
smac89 '18年

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甚至只是myClassabc::testing名称空间中,但C ++不会识别该名称,并且将其检测为冲突。


0

是的,您必须像

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

但是,您试图以不应该使用命名空间的方式使用它们。检查这个问题,也许您会发现它有用。


-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 ++应该采用某些OOP功能,因为C#之所以流行是主要是由于其易用性比较)。


更新:由于C ++ 17具有嵌套的namepacingnuonsoft.com/blog/2017/08/01/c17-nested-namespaces),因此除非您使用的是C ++的较早版本,否则我的答案似乎不再重要。 。
ワイきんぐ

1
尽管我很喜欢Emacs,但发布图像并不理想。它避免了文本搜索/索引编制,也使视觉障碍的访客难以访问您的答案。
海因里希(Heinrich)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.