我从 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++17 可能会简化嵌套命名空间定义:
namespace A::B::C {
}
相当于
namespace A { namespace B { namespace C {
} } }
请参阅 cppreference 上命名空间页面上的 (8):
http://en.cppreference.com/w/cpp/language/namespace
为了避免真正的深度缩进,我通常这样做:
namespace A { namespace B { namespace C
{
class X
{
// ...
};
}}}
clang-format
无法在您显示 clang.llvm.org/docs/ClangFormatStyleOptions.html 时对其进行格式化(命名空间缩进)
我完全支持 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
,但我对预处理器的魔力印象深刻……除非我不必添加额外的辅助宏来进行更深的嵌套……好吧,无论如何我都不会使用它所以...
C++ 命名空间用于对接口进行分组,而不是划分组件或表达政治划分。
该标准不遗余力地禁止使用类似 Java 的命名空间。例如,命名空间别名提供了一种轻松使用深度嵌套或长命名空间名称的方法。
namespace a {
namespace b {
namespace c {}
}
}
namespace nsc = a::b::c;
但是 namespace nsc {}
将是一个错误,因为命名空间只能使用它的 original-namespace-name 来定义。本质上,该标准使此类库的用户 变得容易,但对于实施者 来说却很难。这会阻止人们写这样的东西,但如果他们这样做会减轻影响。
每个接口应该有一个命名空间,由一组相关的类和函数定义。内部或可选子接口可能会进入嵌套命名空间。但是超过两个级别的深度应该是一个非常严重的危险信号。
考虑在不需要 ::
运算符的情况下使用下划线字符和标识符前缀。
company::division
相对于 company_division
的技术优势是什么?
不,请不要那样做。
命名空间的目的主要是解决全局命名空间中的冲突。
次要目的是符号的本地缩写;例如,复杂的 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:这是一个权衡,我们没有硬性数字。谨慎行事,不要朝任何方向过度。 “不要那样做”仅来自“您对此有问题,我对此有问题,而且我看不出您需要它的理由。”。
Constants
的名称空间中,然后创建具有适当名称的嵌套名称空间来对常量进行分类;如有必要,我会使用更多名称空间来防止名称冲突。此 Constants
命名空间本身包含在程序系统代码的包罗万象的命名空间中,其名称如 SysData
。这将创建一个包含三个或四个命名空间(例如 SysData::Constants::ErrorMessages
、SysData::Constants::Ailments::Bitflags
或 SysData::Defaults::Engine::TextSystem
)的完全限定名称。
using
指令来引入适当的名称,从而最大限度地减少名称冲突的可能性。我发现它提高了可读性,并有助于记录任何给定代码块的依赖关系。除了常量之外,如果可能的话,我倾向于尝试将其保留在两个命名空间中(例如 SysData::Exceptions
和 SysData::Classes
)。
这里引用 Lzz (Lazy C++) 文档:
Lzz 识别以下 C++ 结构: 命名空间定义 未命名的命名空间和所有包含的声明都输出到源文件。此规则覆盖所有其他规则。命名空间的名称可以是限定的。命名空间 A::B { typedef int I; } 等价于:命名空间 A { 命名空间 B { typedef int I; } }
当然,依赖于这些工具的资源的质量是有争议的……我想说这更像是一种好奇心,表明 C++ 引起的语法疾病可以有多种形式(我也有我的……)
这篇论文很好地涵盖了这个主题: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++ 不会识别它并且它是检测为碰撞。
两个标准(C++2003 和 C++11)都非常明确地指出命名空间的名称是一个标识符。这意味着需要显式嵌套标题。
我的印象是,除了命名空间的简单名称之外,允许放置合格的标识符并不是什么大问题,但由于某种原因,这是不允许的。
是的,你必须这样做
namespace A{
namespace B{
namespace C{}
}
}
但是,您正在尝试以不应该使用它们的方式使用命名空间。检查this问题,也许您会发现它很有用。
您可以使用以下语法:
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 嵌套命名空间作为标准语言功能 (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# 流行主要是因为它比较易用)。
不定期副业成功案例分享
/std:c++latest
启用/std:c++latest
并同时使用 Boost,则在包含一些 Boost 标头时可能会遇到非常神秘的编译器错误。如in this StackOverflow question所述,我遇到了这个问题