ChatGPT解决这个技术问题 Extra ChatGPT

有没有更好的方法在标头中用 C++ 表达嵌套命名空间

我从 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><std/io/stream> 这样的“IntelliSense”友好语法?我认为默认库中缺少的结构化是 C++ 与 Java/C# 相比的一个弱点。

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

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

更新 2

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

更新 3 - 结论

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

是否滥用 namespace 关键字?
命名空间和 c#/java 模块系统的用途不同,因此您不应尝试以相同的方式使用它们。不,没有更简单的语法,只是因为提供一种语法来使事情更容易做是没有意义的,而这不是应该做的。
@PlasmaHH ...所以缺点是缺少C++标准库的结构化? (请参阅更新中的简单示例)
@Stegi:如果您能给出合理的论据,为什么它会丢失,以及我们将从这种结构化中获得什么实实在在的好处,我们可以谈论潜在的弱点。在那之前,我会称javas无尽的包嵌套充其量是令人困惑的。
@PlasmaHH Intellisense 和其他用于/在标头(包)包含之后的助手。一家公司内的大型项目可能需要不止一个嵌套(例如 vw::golflib::io)才能清楚地说明名称空间在哪个“范围”包含什么。好吧,你可以只使用 vw:: 但是如果命名空间是用来避免冲突的,为什么要声明这么可怕?这最终导致没有人使用它,或者只是使用深度为 1 的命名空间(通常建议)。

o
oHo

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

namespace A::B::C {
}

相当于

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

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


... 由编译器开关 /std:c++latest 启用
请注意,如果您将在 Visual Studio 2015 中使用 /std:c++latest 并同时使用 Boost,则在包含一些 Boost 标头时可能会遇到非常神秘的编译器错误。如in this StackOverflow question所述,我遇到了这个问题
它按原样工作,命名空间 A::B::C。我用 g++-6.0 测试过
K
Kurt Hutchinson

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

namespace A { namespace B { namespace C
{
    class X
    {
        // ...
    };
}}}

JFYI clang-format 无法在您显示 clang.llvm.org/docs/ClangFormatStyleOptions.html 时对其进行格式化(命名空间缩进)
C
Community

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

声明命名空间是 C++ 中非常罕见的情况之一,我实际上喜欢使用 #define

#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,但我对预处理器的魔力印象深刻……除非我不必添加额外的辅助宏来进行更深的嵌套……好吧,无论如何我都不会使用它所以...
P
Potatoswatter

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

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

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

namespace nsc = a::b::c;

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

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

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


好的,那么 Win8 开发中的 Windows::UI::Xaml 和 Windows::UI::Xaml::Controls::Primitives 命名空间呢?我认为微软对命名空间的使用是有道理的,而且它确实比 2 级更深。
使用少于 2 个级别是一个危险信号,使用 3 或 4 级是完全可以的。在没有意义的情况下尝试实现平坦的命名空间层次结构违背了命名空间的目的——避免名称冲突。我同意您应该为接口设置一个级别,为子接口和内部设置另一个级别。但围绕它,您至少需要为公司命名空间增加一层(对于中小型公司)或为公司和部门(对于大公司)增加两层。否则,您的接口名称空间将与在其他地方开发的具有相同名称的其他接口的名称空间发生冲突
@Kaiserludi company::division 相对于 company_division 的技术优势是什么?
@Potatoswatter 在公司内部::anotherDivsion 您可以使用较短的“部门”。即使在您应该强烈避免使用“使用命名空间”来污染更高级别的命名空间的标题中也可以引用 company::division。在公司名称空间之外,您仍然可以执行“使用名称空间公司;”当分区名称不与您范围内的任何其他命名空间发生冲突时,但当某些分区命名空间内的接口名称发生冲突时,您无法执行“使用命名空间 company_division;”。
@Potatoswatter 关键是您几乎可以免费获得它(company::division 不比 company_division 长)并且不必先定义额外的命名空间别名即可使用它。
A
AndyG

不,请不要那样做。

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

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

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

导入外部 COM 库(主要是为了隔离 #import 和 #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 有 about 18000 个类型。)

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

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


好的,那么 Win8 开发中的 Windows::UI::Xaml 和 Windows::UI::Xaml::Controls::Primitives 命名空间呢?我认为微软对命名空间的使用是有道理的,而且它确实比 2 级更深。
如果我需要全局访问常量,我喜欢将它们放在名称类似于 Constants 的名称空间中,然后创建具有适当名称的嵌套名称空间来对常量进行分类;如有必要,我会使用更多名称空间来防止名称冲突。此 Constants 命名空间本身包含在程序系统代码的包罗万象的命名空间中,其名称如 SysData。这将创建一个包含三个或四个命名空间(例如 SysData::Constants::ErrorMessagesSysData::Constants::Ailments::BitflagsSysData::Defaults::Engine::TextSystem)的完全限定名称。
当实际代码中需要常量时,任何需要它们的函数都使用 using 指令来引入适当的名称,从而最大限度地减少名称冲突的可能性。我发现它提高了可读性,并有助于记录任何给定代码块的依赖关系。除了常量之外,如果可能的话,我倾向于尝试将其保留在两个命名空间中(例如 SysData::ExceptionsSysData::Classes)。
总的来说,我想说在一般情况下,最好使用最少数量的嵌套命名空间,但如果出于某种原因需要全局对象(无论是常量还是可变,最好是前者),应该使用多个嵌套命名空间将它们分成适当的类别,以记录它们的使用并最大限度地减少潜在的名称冲突。
-1 表示“请不要这样做”,没有客观原因(尽管稍后进行了澄清)。该语言支持嵌套命名空间,项目可能有充分的理由使用它们。讨论可能的此类原因以及这样做的任何具体、客观的缺点将扭转我的反对意见。
A
Andrii Kalytiiuk

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

Lzz 识别以下 C++ 结构: 命名空间定义 未命名的命名空间和所有包含的声明都输出到源文件。此规则覆盖所有其他规则。命名空间的名称可以是限定的。命名空间 A::B { typedef int I; } 等价于:命名空间 A { 命名空间 B { typedef int I; } }

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


Q
Questionable

这篇论文很好地涵盖了这个主题:Namespace Paper

这基本上归结为这一点。您的命名空间越长,人们使用 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 甚至只是 myClass 仅在 abc::testing 命名空间中,但 C++ 不会识别它并且它是检测为碰撞。


K
Kirill Kobelev

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

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


C
Community

是的,你必须这样做

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

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


s
smac89

您可以使用以下语法:

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 中使用 nested namespace definitions 提供的语法。

快乐的脱巢!

资料来源:

http://seanmiddleditch.com/unnest-c-namespaces/

http://www.nuonsoft.com/blog/2017/08/01/c17-nested-namespaces/


那就是问题中提到的语法,而是寻找更好的解决方案。现在,正如接受的答案所述,使用 C++17 是一个有效的替代方案。抱歉,因为不阅读问题和答案而投反对票。
@Beachwalker 让我们不要拘泥于语法。上面的命名空间声明也可以与接受的答案相同。我想通过这个答案强调的要点是 OP 所说的他错过的内容,以及我在命名空间混乱下所做的事情。据我所见,每个人似乎都专注于声明命名空间中的所有内容,而我的回答让你摆脱了嵌套的混乱,我相信如果 4 年前有人在这个问题时提到过这种语法,OP 会很感激首先被问到。
a
alexpanter

[ 编辑:]
由于 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 { ... } 进行编译。

[原始答案:]由于这个问题有点老了,我会假设你已经继续前进了。但是对于仍在寻找答案的其他人,我提出了以下想法:

https://i.stack.imgur.com/TBliC.png

(我可以在这里为 Emacs 做广告吗:)?)发布图像比简单地发布代码要容易得多,也更易读。我不打算对所有极端案例提供一个完整的答案,只是想提供一些灵感。 (我完全支持 C#,并且觉得 C++ 在很多情况下应该采用一些 OOP 特性,因为 C# 流行主要是因为它比较易用)。


更新:由于 C++17 具有嵌套命名空间 (nuonsoft.com/blog/2017/08/01/c17-nested-namespaces),因此我的答案似乎不再相关,除非您使用的是旧版本的 C++。
尽管我很喜欢 Emacs,但张贴图像并不理想。它避开了文本搜索/索引,并且还使视力受损的访问者难以访问您的答案。