C++命名空间建议

80

我正在自学C++的命名空间(来自C#背景),我真的开始觉得即使在C ++比大多数其他语言做得更好的所有事情中,嵌套命名空间也不是其中之一!

我是否正确地认为,为了声明一些嵌套命名空间,我必须执行以下操作:

namespace tier1
{
    namespace tier2
    {
        namespace tier3
        {
            /* then start your normal code nesting */
        }
    }
}

与之相反:

namespace tier1::tier2::tier3
{
}

如何用C#实现“à la”?

当我需要进行前向声明时,情况变得更加疯狂:

namespace tier1
{
    namespace tier2
    {
        namespace forward_declared_namespace
        {
            myType myVar; // forward declare
        }
        namespace tier3
        {
            /* then start your normal code nesting */
            class myClass
            {
                forward_declared_namespace::myType myMember;
            }
        }
    }
}

考虑到我开发的典型系统包括:

MyCompany::MySolution::MyProject::System::[PossibleSections]::Type

这是为什么C++示例中不太常见使用命名空间吗?或者通常只使用单个(非嵌套)命名空间?

更新

对于任何有兴趣的人,这就是我最终解决这个问题的方法。


11
最终,C++17将允许namespace tier1::tier2::tier3 - underscore_d
8
更新中的链接已损坏... - AlwaysLearning
3
我对了解你如何解决这个问题很感兴趣,于是点击了你的链接。但不幸的是,正如AlwaysLearning已经指出的那样,链接已经失效了。 - csj
9个回答

117

C++ 命名空间并不是设计机制,它们只是为了防止名称冲突而存在。在 99.99% 的情况下,您真的不需要使用嵌套命名空间。

C++ 中正确使用命名空间的一个很好的例子是 C++ 标准库。这个相当大的库中的所有内容都放在一个名为 std 的单一命名空间中 - 没有尝试或需要将库分成(例如)I/O 子命名空间、数学子命名空间、容器子命名空间等。

C++ 中建模的基本工具是类(以及在某种程度上是模板),而不是命名空间。如果您感觉需要嵌套,应该考虑使用嵌套类,它们比命名空间具有以下优点:

  • 它们有方法
  • 它们可以控制访问
  • 它们不能被重新打开

在考虑了这些因素之后,如果您仍然希望使用嵌套命名空间,请随意这样做 - 在这种方式下使用它们没有任何技术上的问题。


19
模板并不是为了成为 mpl 机制而设计的,但是人们却将它们用作这种方式。 - Mykola Golubyev
14
我不想显得苛求小节(我们都知道这是个谎言),但是 <iostream> 是否实现了 std 命名空间内的 ios 命名空间?我目前手边没有 C++ 编译器…… - Binary Worrier
4
@binary ios是basic_ios<char>的typedef。 - anon
7
C++中的命名空间具有特定功能,以更模块化的方式支持接口。请参考Herb Sutter和Andrei Alexandrescu的优秀书籍“C++编程规范”中以下规则:57:将类型及其非成员函数接口放在同一命名空间中。58:将类型和函数放在不同的命名空间中,除非它们被明确用于配合工作。 - alexk7
2
嵌套类在前向声明能力方面会更糟糕:你无法这样做。假设你需要前向声明一个嵌套类型以便将其放入标准容器或智能指针中。如果它是嵌套在一个类中,你就无法这样做。我从多年的C++编程经验中学到了这个教训。任何你需要前向声明的东西都必须在命名空间级别上,而不是嵌套在类中。 - rwong
显示剩余13条评论

25

C++ 命名空间是对以前没有命名空间的情况的巨大改进。 C# 命名空间扩展了该概念并加以利用。我建议您将命名空间保持在一个简单的平面结构中。

编辑 您是否因为我在这里概述的缺点而提出建议?

简单地说,“是的”。C++ 命名空间并不是设计来帮助您像 C# 中那样划分逻辑和库的。

C++ 命名空间的目的是解决 C 开发人员遇到的现实问题,即当使用两个导出相同函数名称的第三方库时遇到名称冲突。 C 开发人员有各种变通方法,但这可能非常麻烦。

其想法是 STL 等具有 std:: 命名空间,由“XYZ 公司”提供的库将具有 xyz:: 命名空间,您为“ABC 公司”工作时会将所有自己的东西放在单个 abc:: 命名空间中。


2
你认为我在这里提到的缺点是否值得注意? - Adam Naylor
1
如果有很多人在“ABC公司”为许多项目工作,并且随着时间的推移,项目合并或分裂,那么至少拥有abc::PROJECT子命名空间是非常有意义的。如果较大的项目具有明确的部分,则abc::PROJECT::SECTION也是有意义的。即使C++之神也已经理解了存在部分,并创建了诸如std::chronostd::ios之类的子命名空间。 - Kai Petzke

20

当我这样进行前向声明时,我该怎么做:

 namespace abc { namespace sub { namespace subsub { class MyClass; }}}

我的前向声明被缩成了一行。为了代码的可读性,我牺牲了前向声明的可读性。对于定义,我也不使用缩进:

 namespace abc {
 namespace sub {
 namespace subsub {
 
 class MyClass 
 {
    public:
       MyClass();

       void normalIntendationsHere() const;
 };

 }
 }
 }

对于我来说,使用这种风格一开始需要一点纪律性,但这是最好的妥协。


自 C++17 起,您可以使用问题作者提出的语法声明命名空间。

namespace A::B::C { ... }

嵌套的命名空间定义:namespace A::B::C { ... } 相当于 namespace A { namespace B { namespace C { ... } } }

https://en.cppreference.com/w/cpp/language/namespace


7
这不是对所问问题的回答。 - Paul Merrill
3
@Jammer:当时我对stackoverflow还很陌生,没有理解这个意思:)抱歉。 - mip

8

至少在某些情况下,您可以采取以下措施:

namespace foo = A::B::C::D;

有时候,需要将A::B::C::D用foo来引用,但这只适用于某些情况。


2
在哪些情况下你不能这样做? - Michael Dorst
问题中的情况只有一个。我想是这样的吧? - Lightness Races in Orbit

6
您可以跳过缩进。我经常这样写。
namespace myLib { namespace details {

/* source code */

} } /* myLib::details */

C++源代码最终编译成二进制文件,不同于C#/Java的是,它留存在二进制中。因此,名称空间(namespace)只是提供了一个解决变量命名冲突的好方法,而不是用来表示类层次结构。

在代码中,我通常保持一个或两个名称空间级别。


4

首先,您可以避免命名空间缩进,因为没有理由这样做。

在示例中使用命名空间无法展示命名空间的威力。对我而言,它们的威力在于将领域区域划分开来。将实用类别与业务类别分开。

只是不要在一个.h文件中混合不同的命名空间层次结构。命名空间是函数声明接口的一种额外注释。查看命名空间和类名应该可以解释很多东西。

namespace product
{
namespace DAO
{

class Entity
{
};

3
首先,您可以避免命名空间缩进,因为没有必要这样做。这正是我实际上所做的 ;) 翻译:首先,您可以避免对命名空间进行缩进,因为这样做没有必要。那正是我实际上所做的;) - Adam Naylor
很好的观点,我认为当使用CASE工具来可视化代码库关系和层次时,命名空间的威力进一步增强。像CppDepend或Visual Studio的Dependency Graph这样的工具采用开发人员构建的“代码可理解性把手”,通过所有命名空间嵌套结构来真正阐明以一种良好结构的连贯方式呈现的层次和依赖关系。 - jxramos

0

我发现你可以通过以下方式模仿C#的命名空间;

namespace ABC_Maths{
    class POINT2{};
    class Complex{};
}

namespace ABC_Maths_Conversion{
    ABC_MATHS::Complex ComplexFromPOINT2(ABC_MATHS::POINT2)
    {return new ABC_MATHS::Complex();}

    ABC_MATHS::POINT4 POINT2FromComplex(ABC_MATHS::COMPLEX)
    {return new ABC_MATHS::POINT2();}
}

namespace ABC
{
}

但代码似乎不是很整洁。我希望有更详细的用法说明。

最好将尽可能多的功能嵌套到类中,例如:

namespace ABC{
    class Maths{
        public:
        class POINT2{};
        class Complex:POINT2{};
        class Conversion{
            public:
            static Maths.Complex ComplexFromPOINT2(MATHS.POINT2 p)
            {return new MATHS.Complex();}

            static MATHS.POINT2 POINT2FromComplex(MATHS.COMPLEX p)
            {return new ABC::MATHS.POINT2();}// Can reference via the namespace if needed
} /*end ABC namespace*/

这还是有点啰嗦,但感觉有点面向对象。

以下是最佳实践:

namespace ABC
{
    class POINT2{};
    class Complex:POINT2{};
    Complex ComplexFromPOINT2(POINT2 p){return new Complex();}
    POINT2 POINT2FromComplex(Complex){return new POINT2();}
}

以下是使用示例

int main()
{
    ABC_Maths::Complex p = ABC_Maths_Conversion::ComplexFromPOINT2(new ABC_MATHS::POINT2());

    // or THE CLASS WAY

    ABC.Maths.Complex p = ABC.Maths.Conversion.ComplexFromPOINT2(new ABC.Maths.POINT2());

    // or if in/using the ABC namespace

    Maths.Complex p = Maths.Conversion.ComplexFromPOINT2(new Maths.POINT2());

    // and in the final case

    ABC::Complex p = ABC::ComplexFromPOINT2(new ABC::POINT2());
}

发现为什么我从来不像在C#中那样使用C++的命名空间很有趣。这会太冗长,而且与C#的命名空间用法完全不同。

C++中最好使用命名空间的地方是防止我的超级cout函数(每次调用都发出叮咚声)与std::cout函数(没那么惊人)混淆。

尽管C#和C++都有命名空间,但这并不意味着命名空间的含义相同。它们是不同但相似的。C#的命名空间的想法肯定来自C++的命名空间。某人肯定看到了一种相似但不同的东西能做些什么,并且没有足够的想象力给它一个独特的名字,比如“类路径”更合适,因为它是指类的路径,而不是提供名称空间的地方,每个空间都可以有相同的名称

希望这对某人有所帮助

我忘了说这些方法都是有效的,并且可以将前两种方法适度结合到第三种方法中,以创建一个有意义的库,例如(不是很好的例子)

_INT::POINT2{}

并且

_DOUBLE::POINT2{}

所以你可以使用以下方式更改精度级别

#define PREC _DOUBLE 
// or #define PREC _INT 

然后创建一个PREC :: POINT2实例,用于双精度POINT2。

使用C#或Java命名空间并不容易。

显然这只是一个例子。想想如何使用。


-2

有时我会在单独的头文件中声明深层命名空间作为一对宏。

namespace.h

#define NAMESPACE_TIER1_TIER2_TIER3 \
    namespace tier1 { \
    namespace tier2 { \
    namespace tier3 {

#define END_NAMESPACE_TIER1_TIER2_TIER3 }}}

在其他地方使用它:

anotherfile.h

#include "./namespace.h"

NAMESPACE_TIER1_TIER2_TIER3;

/* Code here */

END_NAMESPACE_TIER1_TIER2_TIER3;

宏后面的冗余分号是为了避免额外的缩进。


3
这真的很混乱,而且你需要为每个嵌套的命名空间定义它。另外,如果你要写出 NAMESPACE_TIER1_TIER2_TIER3END_NAMESPACE_TEIR1_TIER3_TIER3,为什么不直接写 namespace tier1{namespace tier2{namespace tier3{}}}?总体来说这实际上更短。 - Michael Dorst
我不同意,这并不更短,并且会导致非常深的缩进问题。我已经尝试过两种方法,我更喜欢使用#define方法。编辑:好吧,不考虑缩进问题... - icecream
2
喜欢什么就用什么吧,但我不建议其他用户这样做。这并不会更短(从我的评论中可以看出来),而且还没有算上定义,这种方式肯定更难追踪错误。如果你打错了一个字母怎么办?编译器就不知道哪里出错了,你的错误信息也会显示在完全错误的位置上。 - Michael Dorst
3
“缩短”这个论点并不十分成立,因为它取决于宏的定义方式。至于“拼写错误”这个论点,如果写错命名空间名称,您将会得到与宏解决方案一样难以理解的信息,但使用宏解决方案,许多现代IDE可以解析未定义的名称并帮助您定位错误。命名空间名称只是自由文本,您可以在那里写任何想要的东西,因此,当不使用宏时,避免拼写错误问题实际上更难。建议使用宏解决方案没有任何伤害,人们可以自行决定。 - icecream

-2

你正在过度使用它们(而且你将得不到任何回报)。


15
一个分层有序的结构。(关于“你将得不到任何回报”的说法)我认为这对C ++非常重要,且被高估了。想想它对文档的影响吧。 - Konrad Rudolph
1
@Konrad,我相信这就是我的问题的实质。 - Adam Naylor
这个问题很老了,但对我来说仍然很相关。我曾经和@KonradRudolph有同样的看法,但后来我发现嵌套命名空间是毫无价值的。最好的方法就是只有一个扁平的广泛库命名空间...这很遗憾,但这是我们目前拥有的最好的方法。这也符合std的结构方式。它以这种方式结构化是有原因的 - 目前没有更好的方法。 - user11877195
@KonradRudolph 这是你的问题:其他语言。这个问题严格限于C++命名空间,我没有扩展它的范围。有趣的是,就像C++命名空间一样,我不知怎么地把它现在扩展到了“所有语言中的命名空间”,因为langs::cpp仍然可以看到langs中的所有内容,而没有真正隔离到只有cpp...这正是C++命名空间的问题所在,也是为什么建议使用平面结构。例如,在Rust模块中,您无法从cpp访问langs::*,我从未说过这是不好的,这很好,但在C++中不是这样。 - user11877195
他们不这样做。如果你认为情况并非如此,我建议你与写那篇论文的人争论,并且我真的建议你至少读几遍,它足够简短。从长远来看,几乎没有任何好处,只有弊端,最终你只会打更多的字,而你的代码仍然会像扁平结构一样出现问题,除了扁平结构可以更快地发现问题。 - user11877195
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接