在.cpp文件中定义C ++名称空间方法的正确方法


108

可能是重复的,但搜索起来并不容易...

给定标题,例如:

namespace ns1
{
 class MyClass
 {
  void method();
 };
}

我已经method()在.cpp文件中以几种方式看到了定义:

版本1:

namespace ns1
{
 void MyClass::method()
 {
  ...
 }
}

版本2:

using namespace ns1;

void MyClass::method()
{
 ...
}

版本3:

void ns1::MyClass::method()
{
 ...
}

是否有“正确”的方式来做到这一点?这些“错误”之处是否在于所有含义都不相同?


在大多数代码中,我通常会看到第三个版本(但我不明白为什么:D),第二个版本与为什么我引入名称空间完全相反。
阿努比斯先生2011年

1
有趣的是,Visual Assist重构工具很乐意使用#1或#3生成代码,具体取决于目标文件中已在使用哪种样式。
男孩先生

Answers:


51

版本2不清楚,也不容易理解,因为您不知道哪个名称空间MyClass属于该名称空间,这只是不合逻辑的(类函数不在同一个名称空间中吗?)

版本1是正确的,因为它表明在名称空间中正在定义函数。

版本3也是正确的,因为您使用了::范围解析运算符来引用MyClass::method ()命名空间中的ns1。我更喜欢版本3。

请参阅命名空间(C ++)。这是执行此操作的最佳方法。


22
将#2称为“错误”是一个巨大的夸大。按照这种逻辑,所有符号名称都是“错误的”,因为它们可能会隐藏其他作用域中的其他符号名称。
2011年

这是不合逻辑的。它将编译正常(很抱歉,答案中的错误解释),但是为什么要在名称空间之外定义一个函数呢?它使读者感到困惑。此外,当使用许多名称空间时,它不会显示MyClass属于哪个名称空间。版本1和版本3解决了此问题。总之,这没有错,但只是不清楚和令人困惑。
GILGAMESH 2011年

3
我同意@PhoenicaMacia,使用技巧很糟糕,并且可能导致混乱。考虑一个将操作符实现为自由函数的类,在标题中namespace N {struct X { void f(); }; X operator==( X const &, X const & ); },现在在带有using语句的cpp文件中,您可以将成员函数定义为void X::f() {},但是如果定义的X operator==(X const&, X const&)话,您将定义与该操作符不同的操作符在标题中定义(您必须为其中的free函数使用1或3)。
DavidRodríguez-dribeas 2011年

1
特别是我更喜欢1,而链接文章中的示例并没有真正解决任何问题,第一个示例使用1,第二个示例使用1和3的混合(这些函数是使用限定条件定义的,但是它们是在外部名称空间中定义的)
DavidRodríguez-dribeas 2011年

1
在3种情况中,我会说1)是最好的,但是大多数IDE都习惯于缩进该名称空间声明中的所有内容,这确实增加了一些混乱。
Locka

28

5年后,我以为我会提到这一点,看起来既不错又不邪恶

using ns1::MyClass;

void MyClass::method()
{
  // ...
}

3
这是最好的答案。它看起来最干净,并且避免了OP版本1的问题,后者可能无意将东西带入命名空间,而版本2可能无意将东西带入全局空间。
ayane_m 2016年

是的,这是少于3的键入的很好的组合,同时仍然明确声明了意图。
jb

14

我之所以使用版本4(如下),是因为它结合了版本1(响应性定义的简洁性)和版本3(最大程度地明确)的大多数优点。主要缺点是人们不习惯它,但是由于我认为它在技术上优于我不介意的替代方案。

版本4:使用名称空间别名使用完全限定:

#include "my-header.hpp"
namespace OI = outer::inner;
void OI::Obj::method() {
    ...
}

在我的世界中,我经常使用名称空间别名,因为所有内容都经过明确限定-除非它不能(例如变量名)或它是已知的自定义点(例如函数模板中的swap())。


1
虽然我认为“更好,所以我不在乎它是否会使人感到困惑”的逻辑存在缺陷,但我必须同意这嵌套名称空间的一种好方法。
男孩先生

1
+1是绝佳的“为什么我不认为那个”想法!(至于“人们不习惯[技术先进的新事物]”,如果更多的人这样做,他们会习惯的。)
wjl 2014年

只是为了确保我的理解,都outerinner定义为其他头文件已经命名空间?
dekuShrub

4

版本3使类与名称空间之间的关联非常明确,但以增加键入为代价。版本1避免了这种情况,但捕获了与块的关联。版本2倾向于隐藏此内容,因此我会避免使用它。



3

我选择Num.3(又名冗长的版本)。它的类型更多,但其意图完全适合您和编译器。您原样发布的问题实际上比现实世界中的问题简单。在现实世界中,还有其他定义范围,而不仅仅是类成员。您的定义并不是只对类非常复杂-因为它们的范围从未重新打开(与名称空间,全局范围等不同)。

Num.1可能会在类以外的范围内失败-可以重新打开的任何对象。因此,您可以使用此方法在名称空间中声明一个新函数,否则您的内联函数可能最终会通过ODR进行替换。您将需要一些定义(特别是模板专业化)。

Num.2这非常脆弱,尤其是在大型代码库中-随着标头和依赖项的转移,您的程序将无法编译。

Num.3这是理想的选择,但要输入的内容很多-您的意图是定义某些内容。正是这样做的,并且编译器开始执行操作,以确保您没有犯错,定义不与其声明不同步,等等。



2

所有方法都是正确的,每种方法都有其优点和缺点。

在版本1中,您具有不必在每个函数前面编写名称空间的优点。缺点是您将获得无聊的身份,特别是如果您具有多个级别的名称空间。

在版本2中,您可以使代码更整洁,但是如果CPP中实现了多个名称空间,则一个名称空间可能会直接访问另一名称空间的函数和变量,从而使您的名称空间无用(对于该cpp文件)。

在版本3中,您将必须键入更多内容,并且功能行可能大于屏幕,这对设计效果不利。

有些人还有另一种使用方式。它与第一个版本相似,但是没有识别问题。

就像这样:

#define OPEN_NS1 namespace ns1 { 
#define CLOSE_NS1 }

OPEN_NS1

void MyClass::method()
{
...
}

CLOSE_NS1

由您决定哪种情况适合每种情况=]


14
我看不出在这里使用宏的任何理由。如果您不想缩进,那就不要缩进。使用宏只会使代码不那么明显。
2011年

1
我认为,您要使用不支持名称空间的旧编译器编译代码时,提到的最后一个版本很有用(是的,有些恐龙还在附近)。在这种情况下,您可以将宏放在#ifdef子句中。
卡·马蒂尼

您不需要识别是否不需要,但是如果您不使用宏,则某些IDE会尝试为您执行此操作。例如,在Visual Studio中,您可以选择整个代码,然后按ALT + F8自动识别。如果不使用定义,则将失去该功能。另外,如果您的编码标准中包含OPEN_(namespace)和CLOSE_(namespace),那么我也不认为它不太明显。@LucaMartini给出的原因也很有趣。
Renan Greinert

如果这是通用的,即#define OPEN_NS(X)我认为它有点用,但实际上不是。。。我不反对宏,但这似乎有点OTT。我认为DietmarKühl的方法更适合嵌套名称空间。
男孩先生
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.