命名空间+函数与类上的静态方法


290

假设我有或将要编写一组相关函数。假设它们与数学有关。在组织上,我应该:

  1. 编写这些函数并将它们放在我的MyMath名称空间中,并通过引用它们MyMath::XYZ()
  2. 创建一个名为的类MyMath,并使这些方法静态化,并类似地进行引用MyMath::XYZ()

为什么我会选择一个作为组织软件的方式?


4
一方面,与类和静态方法相比,名称空间是该语言的最新添加,而类和静态方法则是从其被称为“带有类的C”开始使用的。一些程序员可能更喜欢较旧的功能。其他一些程序员可能正在使用旧的编译器。只是我的$ .02
Rom

21
@Rom:您对“旧程序员”是正确的,但对“旧编译器”却是错误的。自从eons起,命名空间已正确编译(我与Visual C ++ 6一起使用,可追溯到1998年!)。对于“带类的C”,发生这种情况的人甚至还不是出生在这个论坛上的人:将其用作避免标准和广泛的C ++功能的说法是谬论。总之,只有过时的C ++编译器不支持名称空间。不要使用该参数作为不使用它们的借口。
paercebal

@paercebal:一些古老的编译器仍在嵌入式世界中使用。不支持名称空间可能是在为每个人每天都与之互动的各种小型CPU编写代码时需要忍受的最小麻烦之一:立体声音响,微波炉,汽车引擎控制单元,交通信号灯等。请明确:我不主张不在各处使用更好,更新的编译器。对比一下:我全都支持最新的语言功能(RTTI;除外)。我只是指出存在这种趋势
Rom

13
@Rom:在当前情况下,问题作者可以选择,因此,显然,他/她的编译器都无法编译命名空间的代码。由于这是有关C ++的问题,因此必须给出C ++答案,包括在需要时提及该问题的名称空间和RTTI解决方案。提供C答案,或为过时的编译器提供C类答案是没有话题的。
paercebal,2011年

2
“只是我的$ .02”-如果您提供了任何不支持名称空间的现存C ++编译器的证据,那么它会更有价值。“某些古老的编译器仍在嵌入式世界中使用” –这很愚蠢;“嵌入式世界”是比C ++名称空间更新的发展。1979年引入了“带有类的C”,远远早于任何人都将C代码嵌入任何东西中。“我只是指出存在这种趋势”-即使存在,也与这个问题无关。
吉姆·巴尔特

Answers:


243

默认情况下,使用命名空间函数。

类是建立对象,而不是替换名称空间。

在面向对象的代码中

斯科特·迈耶斯(Scott Meyers)在他的《有效C ++》一书中写了整篇文章,主题是“将非成员非朋友函数优先于成员函数”。我在Herb Sutter的一篇文章中找到了对此原则的在线参考:http://www.gotw.ca/gotw/084.htm

要知道的重要一点是:在C ++中,与类位于同一名称空间的函数属于该类的接口(因为ADL在解析函数调用时将搜索这些函数)。

除非声明为“ friend”,否则命名空间函数不能访问该类的内部,而静态方法则可以。

例如,这意味着在维护类时,如果需要更改类的内部,则需要在其所有方法中搜索副作用,包括静态方法。

扩展I

将代码添加到类的界面。

在C#中,即使您无权访问它,也可以将方法添加到类中。但是在C ++中,这是不可能的。

但是,即使在C ++中,您仍然可以添加命名空间函数,甚至可以添加到有人为您编写的类中。

从另一方面来看,这在设计代码时很重要,因为通过将函数放在命名空间中,您将授权用户增加/完成类的界面。

扩展二

上一点的副作用是,不可能在多个标头中声明静态方法。每个方法必须在同一类中声明。

对于名称空间,可以在多个标头中声明来自同一名称空间的函数(几乎标准的交换函数就是最好的例子)。

扩展三

名称空间的基本优点是,在某些代码中,如果使用关键字“ using”,则可以避免提及它:

#include <string>
#include <vector>

// Etc.
{
   using namespace std ;
   // Now, everything from std is accessible without qualification
   string s ; // Ok
   vector v ; // Ok
}

string ss ; // COMPILATION ERROR
vector vv ; // COMPILATION ERROR

您甚至可以将“污染”限制为一类:

#include <string>
#include <vector>

{
   using std::string ;
   string s ; // Ok
   vector v ; // COMPILATION ERROR
}

string ss ; // COMPILATION ERROR
vector vv ; // COMPILATION ERROR

为了正确使用几乎标准的交换习惯,此“模式”是必需的。

这与类中的静态方法是不可能的。

因此,C ++名称空间具有自己的语义。

但这更进一步,因为您可以以类似于继承的方式组合名称空间。

例如,如果您具有一个具有功能AAA的名称空间A,具有一个具有功能BBB的名称空间B,则可以声明一个名称空间C,并使用关键字using将AAA和BBB带入该名称空间。

结论

命名空间用于命名空间。类是针对类的。

C ++的设计使每个概念都是不同的,在不同情况下,它们的使用方式也有所不同,以解决不同的问题。

需要名称空间时不要使用类。

在您的情况下,您需要名称空间。


这个答案也可以应用于线程吗,也就是说,对线程使用名称空间而不是静态方法会更好吗?
dashesy 2012年

3
@dashesy:名称空间与静态方法与线程无关,因此,名称空间更好,因为名称空间几乎总是比静态方法更好。一方面,静态方法可以访问类成员变量,所以它们的封装值比名称空间要低。在线程执行中,隔离数据甚至更为重要。
paercebal 2012年

@ paercebal-谢谢,我对线程函数使用了静态类方法。现在,我了解到我正在将类误用作名称空间,那么您认为在一个对象中具有多个线程的最佳方法是什么?我也已经问过这个问题,我很高兴您能提出一些意见(这里或问题本身)
dashesy 2012年

1
@dashesy:您要麻烦。您想要的具有不同线程的方法是隔离不应共享的数据,因此让多个线程具有对类的私有数据的特权访问是一个坏主意。我将在一个类中隐藏一个线程,并确保将该线程的数据与主线程的数据隔离。当然,当时应该共享的数据可以是该类的成员,但是它们仍然应该是同步的(锁,原子等)。我不确定您可以访问多少个库,但是使用任务/异步甚至更好。
paercebal '02

paercebal的答案应该是被接受的!通过命名空间+
ADL-

54

有很多人不同意我,但这就是我的看法:

类本质上是某种对象的定义。静态方法应定义与该对象定义密切相关的操作。

如果您只是要有一组与底层对象或某种对象的定义不相关的相关功能,那么我想说的只是使用名称空间。就我而言,从概念上讲,这要明智得多。

例如,在您的情况下,问自己:“什么是MyMath?” 如果MyMath没有定义一种对象,那么会说:不要使其成为一个类。

但是就像我说的那样,我知道有很多人(甚至是强烈反对)对此表示反对(特别是Java和C#开发人员)。


3
您对此有非常纯粹的看法。但讲,所有的静态方法的类可以派上用场:可以typedef它们,使用它们作为模板参数,等等
Shog9

56
这是因为Jave和C#人别无选择。
马丁·约克2009年

7
@ shog9。您也可以对函数进行模板化!
马丁·约克2009年

6
@Dan:大概是需要数学例程并且想要支持“插入”不同实现的人。
Shog9

1
@Dan:“我认为,如果有人对使用类作为模板参数感兴趣,那么该类几乎肯定会定义一些基础对象。” 一点都不。想一下特质。(尽管如此,我完全同意您的回答。)
sbi

18
  • 如果需要静态数据,请使用静态方法。
  • 如果它们是模板函数,并且您希望能够为所有函数一起指定一组模板参数,则可以在模板类中使用静态方法。

否则,请使用命名空间函数。


针对评论:是的,静态方法和静态数据往往被过度使用。这就是为什么我只提供了两个相关的场景,我认为它们会有所帮助。在OP的特定示例(一组数学例程)中,如果他希望能够指定将应用于所有例程的参数(例如,核心数据类型和输出精度),那么他可以执行以下操作:

template<typename T, int decimalPlaces>
class MyMath
{
   // routines operate on datatype T, preserving at least decimalPlaces precision
};

// math routines for manufacturing calculations
typedef MyMath<double, 4> CAMMath;
// math routines for on-screen displays
typedef MyMath<float, 2> PreviewMath;

如果不需要它,则一定使用名称空间。


2
所谓的静态数据可以是名称空间实现文件中的名称空间级别数据,这可以进一步减少耦合,因为它不必显示在标头中。
Motti

静态数据并不比名称空间范围的全局数据好。
coppro

@coppro。它们至少可以从随机全局变量向进化链迈出一步,因为它们可以被私有化(但另有协议)。
马丁·约克2009年

@Motti:OTOH,如果您在标题(内联/模板函数)中使用它,那您将回到丑陋的状态。
Shog9

1
有趣的示例,使用类作为简写来避免重复template参数!
underscore_d

13

您应该使用名称空间,因为名称空间比类具有许多优势:

  • 您不必在同一标头中定义所有内容
  • 您无需在标头中公开所有实现
  • 你不能using成为班级成员;您可以using命名空间成员
  • 您不能using class,尽管using namespace并非总是那么好主意
  • 使用类意味着实际上没有对象时要创建一些对象

我认为静态成员的使用率非常高。在大多数情况下,它们并不是真正的必需品。静态成员函数可能比文件作用域函数更好,而静态数据成员只是具有更好,当之无愧声誉的全局对象。


3
使用类时,“您也不必在标头中公开所有实现”。
Vanuan 2011年

甚至更多:如果您使用名称空间,则无法在标头中公开所有实现(最终将得到多个符号定义)。内联类成员函数使您可以执行此操作。
Vanuan 2011年

1
@Vanuan:您可以在标头中公开名称空间实现。只需使用inline关键字即可满足ODR。
Thomas Eding

@ThomasEding不需要!=可以
Vanuan

3
@Vanuan:使用时inline,编译器只保证一件事,而不是“内联”函数的主体。的真正(并由标准保证)的目的inline是防止出现多个定义。阅读有关C ++的“一个定义规则”。另外,由于预编译的标头问题而不是ODR问题,因此未编译链接的SO问题。
Thomas Eding

3

我希望使用名称空间,这样您就可以在实现文件的匿名名称空间中拥有私有数据(因此,与private成员相对,它根本不必显示在标头中)。另一个好处是,通过using您的命名空间,方法的客户端可以选择不指定MyMath::


2
您也可以在实现文件的匿名名称空间中使用类来拥有私有数据。不确定我是否遵循您的逻辑。
Patrick Johnmeyer 09年

0

使用类的另一个原因-使用访问说明符的选项。然后,您可以将公共静态方法分解为较小的私有方法。公共方法可以调用多个私有方法。


6
访问修饰符很酷,但是即使大多数private方法也比其原型根本不在标题中发布(因此仍然不可见)的方法更易于访问。我什至没有提到匿名命名空间函数提供的更好的封装。
paercebal 2012年

2
IMO专用方法不如将函数本身隐藏在实现(cpp文件)中,并且从不将其公开在头文件中。请在回答中对此进行详细说明,以及为什么您希望使用私人成员。在那之前-1。
不客气2014年

@nonsensickle也许他的意思是,可以安全地分解具有许多重复节的庞然大物功能,同时将有问题的子节隐藏在私有后面,如果危险/需要非常小心地使用它们,则阻止其他人进入。
Troyseph 2015年

1
即使是@Troyseph,您也可以将此信息隐藏在.cpp文件中未命名的名称空间中,这将使该信息对该翻译单元是私有的,而不会给阅读头文件的任何人任何多余的信息。实际上,我正在努力倡导PIMPL习惯用法。
不知情的

.cpp如果要使用模板,则不能将其放在文件中。
Yay295 '18年

0

命名空间和类方法都有其用途。命名空间具有跨文件分布的能力,但是如果您需要将所有相关代码强制放入一个文件中,则这是一个弱点。如上所述,类还允许您在类中创建私有静态成员。您可以在实现文件的匿名名称空间中使用它,但是与将它们包含在类中相比,它的作用域仍然更大。


“在实现文件的匿名名称空间中[存储内容]比在类中包含它们更大的作用域” –不,不是。在不需要特权访问成员的情况下,匿名命名空间比私有资源更私有private:。在许多情况下,似乎需要特权访问,可以将其排除在外。最“私有”的功能是没有出现在标题中的功能。private:方法永远无法享受这种好处。
underscore_d
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.