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

Namespace + functions versus static methods on a class

提问人:RobertL 提问时间:9/17/2009 最后编辑:Stephen CanonRobertL 更新时间:10/22/2023 访问量:92961

问:

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

  1. 编写这些函数并将它们放在我的命名空间中,并通过以下方式引用它们MyMathMyMath::XYZ()
  2. 创建一个调用的类,并使这些方法成为静态的,并引用类似的MyMathMyMath::XYZ()

为什么我会选择一个而不是另一个作为组织软件的一种方式?

C++ 命名空间 static-methods

评论

5赞 Rom 9/17/2009
首先,与类和静态方法相比,命名空间是该语言中较新的补充,这些方法从它被称为“带有类的 C”时就存在于该语言中。一些程序员可能更习惯于使用较旧的功能。其他一些程序员可能正在使用旧的编译器。只是我的 $.02
26赞 paercebal 9/17/2009
@Rom:你对“老程序员”的看法是对的,但对“老编译器”的看法是错误的。命名空间自很久以前就被正确编译了(我在 Visual C++ 6 中使用了它们,可以追溯到 1998 年!至于“带类的 C”,这个论坛中的一些人甚至还没有出生:以此为论据来避免标准和广泛的 C++ 特性是一种谬误。总之,只有过时的 C++ 编译器不支持命名空间。不要用这个论点作为不使用它们的借口。
1赞 Rom 9/17/2009
@paercebal:一些古老的编译器仍在嵌入式世界中使用。不支持命名空间可能是在为每个人每天与之交互的各种小型 CPU 编写代码时需要忍受的最小不便之一:您的立体声音响、微波炉、汽车中的发动机控制单元、交通信号灯等。需要明确的是:我并不是主张在任何地方都不使用更好、更新的编译器。Au conrare:我完全支持最新的语言功能(RTTI ;)除外)。我只是指出存在这种趋势
14赞 paercebal 1/22/2011
@Rom:在当前情况下,问题作者有选择权,所以显然,他/她的编译器都无法编译命名空间代码。由于这是一个关于C++的问题,因此必须给出C++答案,包括在需要时提及问题的命名空间和RTTI解决方案。给出 C 答案,或者给出 C-with-classes-for-obsolete-compilers 答案是无关紧要的。

答:

21赞 Shog9 9/17/2009 #1
  • 如果需要静态数据,请使用静态方法。
  • 如果它们是模板函数,并且您希望能够同时为所有函数指定一组模板参数,请在模板类中使用静态方法。

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


回应评论:是的,静态方法和静态数据往往被过度使用。这就是为什么我只提供了两个相关的场景,我认为它们可能会有所帮助。在 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;

如果你不需要它,那么一定要使用命名空间。

评论

3赞 Motti 9/17/2009
所谓的静态数据可以是命名空间实现文件中的命名空间级数据,这进一步减少了耦合,因为它不必显示在标头中。
0赞 coppro 9/17/2009
静态数据并不比命名空间范围的全局变量好。
0赞 Martin York 9/17/2009
@coppro。它们至少是随机全局变量进化链上的一步,因为它们可以被私有化(但在其他方面是一致的)。
0赞 Shog9 9/17/2009
@Motti:OTOH,如果你想把它放在标题(内联/模板函数)中,你又回到了丑陋的样子。
1赞 underscore_d 4/8/2016
有趣的例子,使用类作为速记以避免重复的参数!template
62赞 Dan Tao 9/17/2009 #2

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

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

如果你只是要有一组与基础对象或某种对象的定义无关的相关函数,那么我会说只使用命名空间。对我来说,从概念上讲,这要明智得多。

例如,在你的例子中,问问自己,“什么是 MyMath?如果不能定义一种对象,那么我会说:不要让它成为一个类。MyMath

但就像我说的,我知道有很多人会(甚至强烈地)不同意我的观点(特别是 Java 和 C# 开发人员)。

评论

3赞 Shog9 9/17/2009
你对此有一个非常纯粹的看法。但实际上,一个具有全静态方法的类可以派上用场:你可以将它们用作模板参数,等等。typedef
62赞 Martin York 9/17/2009
那是因为 Jave 和 C# 用户别无选择。
7赞 Martin York 9/17/2009
@shog9。您也可以模板化函数!
6赞 Shog9 9/17/2009
@Dan:大概是需要数学例程并希望支持“插入”不同实现的人。
1赞 sbi 9/17/2009
@Dan:“我认为,如果有人有兴趣使用一个类作为模板参数,那么这个类几乎可以肯定是在定义一些底层对象。不,一点也不。想想特质。(尽管如此,我完全同意你的回答。
4赞 Motti 9/17/2009 #3

我更喜欢命名空间,这样你就可以在实现文件的匿名命名空间中拥有私有数据(所以它根本不需要显示在标头中,而不是成员)。另一个好处是,通过命名空间,方法的客户端可以选择不指定privateusingMyMath::

评论

2赞 Patrick Johnmeyer 9/17/2009
您也可以在带有类的实现文件中的匿名命名空间中拥有私有数据。不确定我是否遵循你的逻辑。
12赞 coppro 9/17/2009 #4

您应该使用命名空间,因为命名空间与类相比具有许多优点:

  • 您不必在同一个标头中定义所有内容
  • 您无需在标头中公开所有实现
  • 您不能是班级成员;可以 namespace 成员usingusing
  • 你不能,虽然这通常不是一个好主意using classusing namespace
  • 使用类意味着当实际上没有对象时,要创建一些对象

在我看来,静态成员被过度使用。在大多数情况下,它们并不是真正的必需品。静态成员函数可能更适合作为文件范围函数,而静态数据成员只是具有更好、不值得的声誉的全局对象。

评论

4赞 Vanuan 6/22/2011
“你不需要在标头中公开你的所有实现”,当你使用类时,你也不需要这样做。
0赞 Vanuan 6/22/2011
更多:如果你使用的是命名空间,则无法在标头中公开所有实现(最终会得到多个符号定义)。内联类成员函数允许您这样做。
1赞 Thomas Eding 12/6/2012
@Vanuan:可以在标头中公开命名空间实现。只需使用关键字即可满足 ODR。inline
0赞 Vanuan 12/6/2012
@ThomasEding不需要 != 可以
3赞 Thomas Eding 12/7/2012
@Vanuan:编译器在使用 时只保证一件事,那就是不“内联”函数的主体。它的真正目的(并由标准保证)是防止多个定义。阅读有关 C++ 的“一个定义规则”的信息。此外,由于预编译标头问题而不是 ODR 问题,链接的 SO 问题未编译。inlineinline
288赞 paercebal 9/17/2009 #5

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

类是用来生成对象的,而不是用来替换命名空间的。

在面向对象的代码中

Scott Meyers为他的《有效C++》一书写了一整篇文章,“更喜欢非成员非友元函数而不是成员函数”。我在 Herb Sutter 的一篇文章中找到了对这一原则的在线引用: http://www.gotw.ca/gotw/084.htm

需要了解的重要一点是:在 C++ 中,与类位于同一命名空间中的函数以及将该类作为参数的函数属于该类的接口(因为 ADL 在解析函数调用时会搜索这些函数)。

例如:

  • 假设您有一个命名空间 N
  • 假设您有一个类 C,在命名空间 N 中声明(换句话说,它的全名是 N::C )
  • 假设您有一个函数 F,在命名空间 N 中声明(换句话说,它的全名是 N::F )
  • 假设函数 F 在其参数中有一个 C 类型的参数

...那么 N::F 是 N:C 公共接口的一部分。

命名空间函数,除非声明为“friend”,否则无权访问类的内部结构,而静态方法有权访问类的内部结构。

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

扩展 I

向类接口添加代码。

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

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

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

扩展 II

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

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

扩展 III

命名空间的基本酷之处在于,在某些代码中,如果您使用关键字,则可以避免提及它: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++ 命名空间有自己的语义。

但它走得更远,因为您可以以类似于继承的方式组合命名空间。

例如,如果你有一个带有函数的命名空间,一个带有函数的命名空间,你可以声明一个命名空间,并在此命名空间中加入关键字。AAAABBBBCAAABBBusing

您甚至可以将一个命名空间的全部内容带入另一个命名空间,如命名空间 D 所示!using namespace

namespace A
{
   void AAA();
   void AAA2();
}

namespace B
{
   void BBB();
}

namespace C
{
   using A::AAA;
   using B::BBB;
}

namespace D
{
   using namespace A;
   using namespace B;
}

void foo()
{
   C::AAA();
   // C::AAA2(); // ERROR, won't compile
   C::BBB();
}

void bar()
{
   D::AAA();
   D::AAA2();
   D::BBB();
}

结论

命名空间用于命名空间。 课程是为课程准备的。

C++ 的设计使得每个概念都是不同的,并且在不同的情况下以不同的方式使用,作为不同问题的解决方案。

当需要命名空间时,不要使用类。

在你的例子中,你需要命名空间。

评论

0赞 dashesy 2/18/2012
这个答案也可以应用于线程吗,即对线程使用命名空间而不是静态方法是否更好?
3赞 paercebal 2/18/2012
@dashesy :命名空间与静态方法与线程无关,所以是的,命名空间更好,因为命名空间几乎总是比静态方法更好。如果说一件事,静态方法可以访问类成员变量,因此它们在某种程度上具有比命名空间更低的封装值。在线程执行中,隔离数据更为重要。
0赞 dashesy 2/18/2012
@paercebal - 谢谢,我正在对线程函数使用静态类方法。现在我明白我滥用了类作为命名空间,那么您认为在一个对象中拥有多个线程的最佳方法是什么?我也在 SO 上问过这个问题,如果您能提供一些说明(此处或问题本身),我将不胜感激
1赞 paercebal 2/18/2012
@dashesy : 你是自找麻烦。您希望使用不同的线程来隔离不应该共享的数据,因此让多个线程有权访问一个类的私有数据是一个坏主意。我会将一个线程隐藏在一个类中,并确保将该线程的数据与主线程的数据隔离开来。当然,应该共享的数据可以是该类的成员,但它们仍然应该同步(锁、原子等)。我不确定您可以访问多少库,但使用 tasks/async 会更好。
1赞 RoundPi 10/5/2012
Paercebal 的答案应该是公认的!通过命名空间 + ADL -> stackoverflow.com/questions/6380862/ 为几乎标准的 swap() 再增加一个链接......
0赞 Milind T 11/13/2011 #6

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

评论

7赞 paercebal 2/18/2012
访问修饰符很酷,但即使是大多数方法也比原型根本没有在标头中发布(因此仍然不可见)的方法更容易访问。我什至没有提到匿名命名空间函数提供的更好的封装。private
2赞 nonsensickle 3/31/2014
IMO,私有方法不如将函数本身隐藏在实现(cpp 文件)中并且从不在头文件中公开它。请在您的回答中详细说明这一点,以及您为什么更愿意使用私人会员。在那之前,-1。
0赞 Troyseph 11/13/2015
@nonsensickle 也许他的意思是,一个具有许多重复部分的庞大功能可以安全地分解,同时将违规的小节隐藏在私密后面,如果它们很危险/需要非常小心地使用,可以阻止其他人使用它们。
1赞 nonsensickle 11/15/2015
@Troyseph即便如此,您也可以将此信息隐藏在文件中的未命名命名空间中,这将使其对该翻译单元私有,而不会向读取头文件的任何人提供任何多余的信息。实际上,我试图倡导 PIMPL 成语。.cpp
0赞 Yay295 7/17/2018
如果要使用模板,则不能将其放入文件中。.cpp
0赞 rocd 4/16/2015 #7

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

评论

0赞 underscore_d 4/8/2016
“[将内容存储在实现文件的匿名命名空间中]比将它们放在类中的范围更大” - 不,它不是。 在不需要对成员进行特权访问的情况下,匿名命名空间的东西比 Ones 更私密。在许多情况下,似乎需要特权访问这可以被排除在外。最“私密”的函数是不出现在标头中的函数。 方法永远无法享受这种好处。private:private:
5赞 alfC 3/28/2021 #8

我想总结一下并补充其他答案。此外,我的观点是仅标题的世界。


命名空间

优点:

  • 命名层次结构的简单解决方案
  • 它们没有语义,因此更易于阅读
  • 可以存在于不同的文件(标头)中
  • 可以扩展
  • ADL公司
  • 快捷方式可以定义 ()。using
  • 在操作员过载时表现良好
  • 可用于品牌推广(您可以设计代码并在其上放置一个命名空间,而无需太多)

缺点:

  • 一切都是公开的
  • 私有事物需要未命名的命名空间,因此它不是显式的
  • ADL(是的,有些人鄙视ADL)
  • 可以扩展(这可能是一件坏事,特别是与 ADL 结合使用时,现有代码的语义可以通过扩展命名空间来更改)
  • 函数需要按使用顺序定义(或声明)

具有静态方法的类

优点:

  • 可以有私有组件(函数、变量),并且它们被显式标记。
  • 班级可以成为朋友
  • 可以进行类型参数化(模板)
  • 可以是模板参数本身
  • 可以实例化
  • 可以传递给函数(默认情况下,静态函数的行为类似于非静态方法)。
  • 更容易找到模式,并从独立函数组中转换它们,并将它们转换为适当的类(最终使用非静态成员)
  • 类之间的依赖关系定义明确
  • 函数(静态方法)可以按任何顺序定义

缺点:

  • 无 ADL
  • 无法扩展
  • 需要关键字static everywhere(取笑语言的机会)
  • 单独解决命名问题有点矫枉过正。在这种情况下很难阅读。
  • 函数(静态方法)始终需要限定 ()。无法声明快捷方式 ()。myclassspace::funusing
  • 对于操作员过载几乎毫无用处,需要复杂的友元机制。
  • 不能用于品牌推广。
  • 你需要记住,以:)结束它;

总之,具有静态方法的类是更好的代码单元,允许更多的元编程,除了 ADL 和一些语法怪癖之外,可以复制命名空间的所有功能,但有时它们可能有点矫枉过正。

像彭博社这样的公司更喜欢类而不是命名空间。 如果您不喜欢 ADL 或运算符重载,那么使用静态方法的类是您的不二之选。

IMO,如果将命名空间和类集成在一起成为同一枚硬币的两面,那就太好了。 例如,将语言中的命名空间标识为类,默认情况下,这些方法是静态的。 然后能够将它们用作模板参数。 我不确定如何处理 ADL(可能它可能仅限于符号运算符函数,例如 operatorX,这是运算符重载和 ADL 的最初动机)

2赞 Boann 1/20/2022 #9

为什么我会选择一个而不是另一个作为组织软件的一种方式?

如果使用命名空间,则经常会遇到语言缺陷,即相互调用的函数必须按特定顺序列出,因为 C++ 无法在文件中看到更深的定义。

如果使用类,则不会发生此缺陷。

将实现函数包装在一个类中可能比维护所有实现函数的声明或将它们按不自然的顺序排列以使其编译更容易、更干净。

0赞 disservin 10/22/2023 #10

如果要构建仅标头库,则不会有实现,因此匿名命名空间不起作用。 在这种情况下,您可能希望定义一个公开静态公共函数的类,并使您不希望公开的逻辑成为私有的。 即

namespace mylib {

class utils {
  static int bar() { return 42; }
 public:
  static int foo() {
    return bar();
  }
}

}