【问题标题】:Namespace + functions versus static methods on a class命名空间 + 函数与类上的静态方法
【发布时间】:2010-11-28 22:16:27
【问题描述】:

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

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

为什么我会选择其中一个来组织我的软件?

【问题讨论】:

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

标签: c++ namespaces static-methods


【解决方案1】:
  • 如果您需要静态数据,请使用静态方法。
  • 如果它们是模板函数,并且您希望能够同时为所有函数指定一组模板参数,那么请在模板类中使用静态方法。

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


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

如果您不需要它,那么无论如何使用命名空间。

【讨论】:

  • 所谓的静态数据可以是命名空间的实现文件中的命名空间级别的数据,这样就更加减少了耦合,因为它不必出现在header中。
  • 静态数据并不比命名空间范围的全局变量好。
  • @coppro。它们至少比随机全局变量的进化链更上一层楼,因为它们可以私有化(但在其他方面同意)。
  • @Motti:OTOH,如果你在标题中想要它(内联/模板函数),你又会变得丑陋。
  • 有趣的例子,使用类作为速记以避免重复template 参数!
【解决方案2】:

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

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

如果您只想拥有一组与底层对象无关的相关函数或某种对象的定义,那么我会说只使用命名空间。就我而言,从概念上讲,这更明智。

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

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

【讨论】:

  • 您对此有非常纯粹的看法。但实际上,一个全静态方法的类可以派上用场:你可以typedef它们,将它们用作模板参数等等。
  • 那是因为 Jave 和 C# 人别无选择。
  • @shog9。你也可以模板化函数!
  • @Dan:大概是需要数学例程并希望支持“插入”不同实现的人。
  • @Dan:“我认为如果有人对使用类作为模板参数感兴趣,那么该类几乎肯定会定义一些底层对象。”一点都不。想想特质。 (不过,我完全同意你的回答。)
【解决方案3】:

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

【讨论】:

  • 您也可以在带有类的实现文件中的匿名命名空间中拥有私有数据。不确定我是否遵循您的逻辑。
【解决方案4】:

你应该使用命名空间,因为命名空间比类有很多优点:

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

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

【讨论】:

  • “你不需要在标题中公开你的所有实现”当你使用一个类时你也不需要。
  • 甚至更多:如果您使用命名空间,则无法在标头中公开所有实现(您最终会得到多个符号定义)。内联类成员函数允许您这样做。
  • @Vanuan:您可以在标头中公开命名空间实现。只需使用inline 关键字即可满足ODR。
  • @ThomasEding 不需要!= 可以
  • @Vanuan:编译器在使用inline 时只保证一件事,它不是“内联”函数体。 inline真实(并由标准保证)目的是防止多重定义。阅读有关 C++ 的“一个定义规则”。此外,由于预编译的标头问题而不是 ODR 问题,链接的 SO 问题未编译。
【解决方案5】:

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

类用于构建对象,而不是替换命名空间。

在面向对象的代码中

Scott Meyers 为他的 Effective C++ 书写了一整篇关于这个主题的文章,“Prefer non-member non-friend functions to member functions”。我在 Herb Sutter 的一篇文章中找到了对这一原则的在线参考:http://www.gotw.ca/gotw/084.htm

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

例如:

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

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

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

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

扩展 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++ 命名空间有自己的语义。

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

例如,如果你有一个命名空间A和一个函数AAA,一个命名空间B和一个函数BBB,你可以声明一个命名空间C,并带上AAA和@987654333 @ 在此命名空间中使用关键字 using

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

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++ 的设计使每个概念都是不同的,并且在不同的情况下以不同的方式使用,作为不同问题的解决方案。

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

在你的情况下,你需要命名空间。

【讨论】:

  • 这个答案也可以应用于线程,即使用命名空间而不是线程的静态方法更好吗?
  • @dashesy : 命名空间与静态方法 与线程无关,所以是的,命名空间更好,因为命名空间几乎总是比静态方法更好。一方面,静态方法可以访问类成员变量,因此它们的封装值比命名空间低。而隔离数据在线程执行中更为重要。
  • @paercebal- 谢谢,我使用的是线程函数的静态类方法。现在我明白我在滥用类作为命名空间,那么您认为在一个对象中拥有多个线程的最佳方法是什么?我也曾在 SO 上问过这个问题,如果您能提供一些启发(此处或问题本身),我将不胜感激
  • @dashesy:你是在自找麻烦。您想要使用不同的线程来隔离不应该共享的数据,因此让多个线程具有对类的私有数据的特权访问是一个坏主意。我会将 one 线程隐藏在一个类中,并确保将该线程的数据与主线程的数据隔离开来。当然,应该共享的数据可以是该类的成员,但它们仍然应该是同步的(锁、原子等)。我不确定您可以访问多少库,但使用 tasks/async 会更好。
  • paercebal 的回答应该是公认的!通过命名空间 + ADL -> stackoverflow.com/questions/6380862/… 为几乎标准的 swap() 再添加一个链接
【解决方案6】:

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

【讨论】:

  • 访问修饰符很酷,但即使是最常用的private 方法也比原型根本没有在标题中发布的方法更容易访问(因此,它仍然不可见)。我什至没有提到匿名命名空间函数提供的更好的封装。
  • IMO,私有方法不如将函数本身隐藏在实现(cpp 文件)中并且从不将其暴露在头文件中。请在您的回答中详细说明这一点,以及为什么您更愿意使用 private 成员。在那之前-1。
  • @nonsensickle 也许他的意思是可以安全地分解具有许多重复部分的庞大函数,同时将有问题的子部分隐藏在 private 后面,如果它们是危险的/需要非常小心使用,则阻止其他人获取它们。
  • @Troyseph 即便如此,您也可以将此信息隐藏在 .cpp 文件中的未命名命名空间中,这将使该翻译单元私有,而不会向任何阅读头文件的人提供任何多余的信息。实际上,我正在努力倡导 PIMPL 成语。
  • 如果你想使用模板,你不能把它放在.cpp文件中。
【解决方案7】:

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

【讨论】:

  • “在实现文件的匿名命名空间中[存储东西] [是] 比将它们放在类中更大的范围” - 不,不是。在不需要对成员进行特权访问的情况下,匿名命名空间的内容比private: 的内容更私密。在许多情况下,似乎需要特权访问,这可以被排除在外。最“私人”的功能是没有出现在标题中的功能。 private: 方法永远无法享受这种好处。
【解决方案8】:

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


命名空间

优点:

  • 命名层次结构的简单解决方案
  • 它们没有语义,因此更易于阅读
  • 可以存在于不同的文件(标题)中
  • 可以扩展
  • ADL
  • 可以定义快捷方式 (using)。
  • 与运算符重载配合得很好
  • 可用于品牌推广(您可以设计代码并在其上放置命名空间,但无需太多)

缺点:

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

具有静态方法的类

优点:

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

缺点:

  • 无 ADL
  • 无法扩展
  • 到处都需要关键字 static(有机会取笑这种语言)
  • 单独解决命名问题的做法太过分了。在这种情况下很难阅读。
  • 函数(静态方法)总是需要限定(myclassspace::fun)。无法声明快捷方式 (using)。
  • 对于运算符重载几乎没用,需要复杂的友元机制。
  • 不能用于品牌推广。
  • 您需要记住以; 结束它:)

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

Bloomberg 等公司更喜欢类而不是命名空间。 如果您不喜欢 ADL 或运算符重载,则可以使用具有静态方法的类。

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

【讨论】:

    【解决方案9】:

    为什么我会选择其中一个来组织我的软件?

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

    如果使用类,则不会出现此缺陷。

    将实现函数包装在一个类中比维护所有函数的声明或以不自然的顺序使其编译更容易和更清晰。

    【讨论】:

      猜你喜欢
      • 2011-12-09
      • 1970-01-01
      • 2010-09-14
      • 2014-11-13
      • 2021-11-18
      相关资源
      最近更新 更多