116

我从 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 不使用“智能感知”友好语法,例如#include <std::io::stream>or <std/io/stream>?我认为默认库中缺少的结构化是 C++ 与 Java/C# 相比的一个弱点。

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

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

更新 2

大多数用户说创建比两个级别更深的嵌套是无稽之谈。好的,那么 Win8 开发中的 Windows::UI::Xaml 和 Windows::UI::Xaml::Controls::Primitives 命名空间呢?我认为微软对命名空间的使用是有道理的,而且它确实比 2 级更深。我认为更大的库/项目需要更深的嵌套(我讨厌像 ExtraLongClassNameBecauseEveryThingIsInTheSameNameSpace 这样的类名......那么你也可以将所有内容放入全局命名空间。)

更新 3 - 结论

大多数人说“不要这样做”,但是……即使是 boost 的嵌套也比一两层更深。是的,它是一个库,但是:如果您想要可重用的代码 - 将您自己的代码视为您将提供给其他人的库。我还使用更深的嵌套来使用命名空间进行发现。

4

11 回答 11

161

C++17 可能会简化嵌套命名空间定义:

namespace A::B::C {
}

相当于

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

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

于 2015-01-19T10:25:50.613 回答
31

为了避免真正的深度缩进,我通常这样做:

namespace A { namespace B { namespace C
{
    class X
    {
        // ...
    };
}}}
于 2014-12-12T15:40:46.080 回答
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;

对于比三层更深的嵌套,您必须将辅助宏添加到所需的数量。

于 2014-12-17T19:09:10.473 回答
13

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

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

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

namespace nsc = a::b::c;

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

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

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

于 2012-07-06T08:36:36.260 回答
5

这里引用Lzz (Lazy C++) 文档:

Lzz 识别以下 C++ 结构:

命名空间定义

未命名的命名空间和所有包含的声明都输出到源文件。此规则覆盖所有其他规则。

命名空间的名称可以是限定的。

   namespace A::B { typedef int I; }

相当于:

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

当然,依赖于这些工具的资源的质量是值得商榷的......我会说这更像是一种好奇心,表明 C++ 引起的语法疾病可以有多种形式(我也有我的......)

于 2012-07-06T08:52:37.620 回答
5

不,请不要那样做。

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

次要目的是符号的本地缩写;例如,一个复杂的UpdateUI方法可以使用一个using namespace WndUI来使用更短的符号。

我在一个 1.3MLoc 项目中,我们拥有的唯一命名空间是:

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

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

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

也很少需要“公司”名称空间,除非您对以二进制形式分发的 3rd 方库有很大问题,不提供自己的名称空间,并且不能轻易放入其中(例如,在二进制文件中)分配)。尽管如此,根据我的经验,强制它们进入命名空间要容易得多。


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

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

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

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

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

我会进一步假设命名空间中有一个最佳(数量级)符号。比如说,1 没有意义,100 听起来不错,10000 显然太多了。


TL;DR:这是一个权衡,我们没有硬性数字。谨慎行事,不要朝任何方向过度。“不要那样做”仅来自“您对此有问题,我对此有问题,而且我看不出您需要它的理由。”。

于 2012-07-06T09:00:15.773 回答
2

两个标准(C++2003 和 C++11)都非常明确地指出命名空间的名称是一个标识符。这意味着需要显式嵌套标题。

我的印象是,除了命名空间的简单名称之外,允许放置合格的标识符并不是什么大问题,但由于某种原因,这是不允许的。

于 2012-07-06T08:22:48.513 回答
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;那么“测试”名称空间将变得不明确,并且您将遇到命名冲突。这意味着您的代码库可以通过包含 3rd 方库从稳定变为不稳定。

在 C# 中,即使您要使用using abc;并且using def;编译器能够识别它testing::myClass,甚至只是myClassabc::testing命名空间中,但 C++ 不会识别它并且它被检测为冲突。

于 2020-02-18T15:21:43.287 回答
0

是的,你必须这样做

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

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

于 2012-07-06T08:49:42.537 回答
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 中使用嵌套命名空间定义的语法。

快乐的脱巢!

资料来源:

于 2018-01-02T05:32:36.743 回答
0

[ 编辑:]
由于支持 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# 流行主要是因为它比较易用)。

于 2018-10-22T07:31:05.267 回答