假设我已经或将要编写一组相关函数。假设它们与数学有关。在组织上,我应该:
编写这些函数并将它们放在我的MyMath名称空间中,然后通过MyMath::XYZ()引用它们。
创建一个名为MyMath的类,使这些方法成为静态的,并引用类似的MyMath::XYZ()。
为什么我会选择其中一个来组织我的软件?
- 首先,与类和静态方法相比,名称空间是语言的最新添加,后者从被称为"C with classes"时起就在语言中。有些程序员可能更愿意使用旧特性。其他一些程序员可能正在使用旧的编译器。我的02美元
- @罗:你说"老程序员"是对的,但说"老编译器"是错的。命名空间自EONS(我用Visual C++ 6,从1998开始工作)被正确编译了!至于"C类",在这个论坛中的一些人甚至没有出生时发生的事情:使用这个作为一个论点来避免一个标准的和广泛的C++特征是一个谬误。总之,只有过时的C++编译器不支持命名空间。不要把那个论点当作不使用它们的借口。
- @paercebal:一些古代的编译器还在嵌入式世界中使用。不支持名称空间可能是为每个人每天都与之交互的各种小CPU编写代码时需要忍受的最小的不便之一:立体声、微波炉、车内发动机控制单元、交通灯等。只是要清楚:我不主张在任何情况下都不使用更好、更新的编译器。重新。au conrelar:我完全赞成最新的语言特性(rtti;)除外)。我只是指出这种趋势存在
- @罗姆:在目前的情况下,问题的作者有选择权,所以很明显,他/她的编译器没有一个会编译名称空间代码。由于这是一个关于C++的问题,必须给出C++的答案,包括在需要时提到这个问题的命名空间和RTTI解决方案。给出一个C答案,或者一个C-with-classes-for-obsolete-compilers答案是不合适的。
- "只是我的02美元"——如果你提供了不支持命名空间的现存C++编译器的任何证据,那就更有价值了。一些古老的编译器仍然在嵌入式世界中使用——这是愚蠢的;"嵌入式世界"是比C++命名空间更为新近的发展。c with classes"是1979年引入的,早在任何人在任何东西中嵌入c代码之前。我只是指出这样一种倾向的存在——即使如此,这与这个问题无关。
默认情况下,使用名称空间函数。
类是构建对象,而不是替换命名空间。
在面向对象的代码中
Scott Meyers在这个主题上为他的有效C++书写了一个完整的条目,"更喜欢非成员非朋友函数,而不是成员函数"。我在Herb Sutter:http://www.gotw.ca/gotw/084.htm的一篇文章中找到了这一原则的在线参考。
重要的是要知道:与类相同的命名空间中的C++函数属于该类的接口(因为在解析函数调用时ADL将搜索这些函数)。
命名空间函数,除非声明为"friend",否则不能访问类的内部,而静态方法具有。
例如,这意味着,在维护类时,如果需要更改类的内部结构,则需要在其所有方法中搜索副作用,包括静态方法。
扩展I
向类的接口添加代码。
在C中,您可以向类添加方法,即使您无权访问它。但是在C++中,这是不可能的。
但是,在C++中,仍然可以添加命名空间函数,甚至是为某人编写的函数。
另一方面,这在设计代码时很重要,因为通过将函数放在名称空间中,您将授权用户增加/完成类的接口。
扩展二
前一点的副作用是,不可能在多个头中声明静态方法。每个方法都必须声明在同一类中。
对于名称空间,可以在多个头中声明来自同一名称空间的函数(几乎标准的swap函数就是最好的例子)。
扩展三
名称空间的基本酷性在于,在某些代码中,如果使用关键字"using",则可以避免提及它:
1 2 3 4 5 6 7 8 9 10 11 12 13
| #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 |
你甚至可以把"污染"限制在一个级别上:
1 2 3 4 5 6 7 8 9 10 11
| #include <string>
#include <vector>
{
using std::string ;
string s ; // Ok
vector v ; // COMPILATION ERROR
}
string ss ; // COMPILATION ERROR
vector vv ; // COMPILATION ERROR |
为了正确使用几乎标准的交换习惯用法,必须使用这个"模式"。
这对于类中的静态方法来说是不可能的。
因此,C++命名空间有自己的语义。
但它更进一步,因为您可以以类似于继承的方式组合名称空间。
例如,如果您有一个带有函数a a a的命名空间A,一个带有函数b b b的命名空间B,那么您可以声明一个命名空间C,并使用关键字将aaa和bbb带到这个命名空间中。
结论
命名空间用于命名空间。上课是为了上课。
C++被设计成每个概念是不同的,并且在不同的情况下被不同地使用,作为不同问题的解决方案。
需要名称空间时不要使用类。
在您的例子中,您需要名称空间。
- 这个答案是否也可以应用于线程,也就是说,对线程使用名称空间而不是静态方法更好?
- @dashesy:名称空间与静态方法与线程无关,因此是的,名称空间更好,因为名称空间几乎总是比静态方法更好。如果有一点,静态方法可以访问类成员变量,因此它们的封装值比名称空间要低。隔离数据在线程执行中更为重要。
- @paercebal-谢谢,我使用了线程函数的静态类方法。现在我了解到我滥用类作为名称空间,那么您认为在一个对象中拥有多个线程的最佳方法是什么?我也问过这个问题,如果你能(在这里或问题本身)透露一些信息,我很感激。
- @达西:你在自找麻烦。对于不同的线程,您需要隔离不应该共享的数据,因此让多个线程有权访问类的私有数据是一个坏主意。我将在一个类中隐藏一个线程,并确保将该线程的数据与主线程的数据隔离开来。当然,应该共享的数据可以是该类的成员,但它们仍然应该是同步的(锁、原子等)。我不确定您可以访问多少libs,但是使用tasks/async更好。
- Paercebal的答案应该是被接受的!只需再通过namespace+adl->stackoverflow.com/questions/6380862/&hellip为几乎标准的swap()提供一个链接;
- 你能解释一下如何"添加一个名称空间函数,甚至是一个类"吗?链接的Sutter文章似乎建议,例如,std::string::erase应该在类之外,并且应该是:clear(std::string &s) { s.erase(s.begin(), s.end()); }。也许它更具可扩展性,但是必须检查"Do I do s.clear()or clear(s)"是非常讨厌的。为了一切。另外,它感觉像面向对象的C。也许我误解了什么?
- @ PrimeTe:在CLSISP中,函数的调用(包括"虚拟")被写为(funcname arg1 arg2 arg3),因此Java类符号EDCOX1(2)不应被认为是准许的…与Java和C语言不同,在C++中,函数并没有死掉。一旦你克服了这一点,"恼人的因素"就会退后一步…我建议你搜索一个关于"object .Mult.方法()"的问题与C++的"方法(对象)",或者你自己问一下,如果你想要每个符号的优点/缺点的列表。
- 扩展III似乎将使用声明(using std::string;与使用指令(using namespace std;混为一谈,没有将它们清楚地分开,这可能会给人错误的印象,为了正确执行掉期,您需要一个使用声明。就我个人而言,我发现使用指令是错误的,大多数人都不能正确理解,你应该避免使用指令。特别是如果您打算按照这个块中最后一段的建议(将来自不同名称空间的名称放在一起)。
有很多人不同意我的观点,但我是这样看的:
类本质上是某种对象的定义。静态方法应该定义与该对象定义密切相关的操作。
如果您只需要一组与底层对象或某种对象的定义没有关联的相关函数,那么我会说只使用名称空间。就我而言,从概念上讲,这更明智。
例如,在您的案例中,问自己,"什么是mymath?"如果MyMath没有定义一种对象,那么我会说:不要把它变成类。
但正如我所说的,我知道有很多人会(甚至强烈地)反对我(尤其是Java和C开发人员)。
- 你对此有一个非常纯粹的观点。但实际上,具有所有静态方法的类都可以派上用场:您可以使用cx1〔1〕它们,将它们用作模板参数等。
- 这是因为哈维和C_人没有选择。
- @世茂9号。您也可以将函数模板化!
- @马丁:是的,当然。我想我的观点是你使用的工具倾向于塑造你的思维方式。
- @马丁.约克:你能把一个名称空间作为模板参数传递吗?
- @Shog9:我认为如果有人想使用类作为模板参数,那么该类几乎肯定定义了一些底层对象。以这个问题为例:什么样的模板函数会把mymath作为参数?
- @丹:大概是,一个需要数学程序并且想要支持"插入"不同实现的程序。
- @Shog9:但这并不是说我不欣赏你的务实态度。使用静态类的额外好处当然不是什么。
- @丹:出于同样的原因,我不想在这里推广静态类——它们被过度使用了,至少在我见过的大部分代码中是这样。但它们确实有它们的用途。
- @丹:"我认为,如果有人有兴趣使用类作为模板参数,那么该类几乎肯定是在定义一些底层对象。"不,一点也不。想想特性。(不过,我完全同意你的回答。)
- @肖格:从来没有考虑过模板参数。很不错的。@马丁:不能将命名空间mymath用作模板参数。我想这就是肖格所说的。
- 如果需要静态数据,请使用静态方法。
- 如果它们是模板函数,并且您希望能够一起为所有函数指定一组模板参数,那么在模板类中使用静态方法。
否则,请使用名称空间函数。
对评论的回应是:是的,静态方法和静态数据往往被过度使用。这就是为什么我只提供了两个相关的场景,我认为它们会有帮助。在OP的特定示例(一组数学例程)中,如果他希望能够指定参数(例如,核心数据类型和输出精度),并将其应用于所有例程,他可能会做如下操作:
1 2 3 4 5 6 7 8 9 10
| 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; |
如果您不需要它,那么一定要使用名称空间。
- 所谓的静态数据可以是名称空间实现文件中的名称空间级数据,这会进一步减少耦合,因为它不必出现在头中。
- 静态数据不比命名空间范围全局数据好。
- @科普罗。它们至少是从随机全球进化链上的一个步骤,因为它们可以被私有化(但另有约定)。
- @莫蒂:哦,如果你想把它放在头上(内联/模板函数),那你就要重新变得丑陋了。
- 有趣的例子是,使用类作为速记来避免重复template参数!
应该使用名称空间,因为名称空间比类具有许多优势:
- 您不必在同一个标题中定义所有内容
- 您不需要在头中公开所有实现
- 不能是类成员;可以是命名空间成员
- 你不能用using class,尽管using namespace不是一个好主意。
- 使用类意味着当实际上没有对象时,有一些对象要创建
在我看来,静态成员被过度使用了。在大多数情况下,它们不是真正的必需品。静态成员函数作为文件作用域函数可能更好,静态数据成员只是具有更好的、不受欢迎的声誉的全局对象。
- "不需要在头中公开所有实现",在使用类时也不需要这样做。
- 更重要的是:如果您使用的是名称空间,那么就不能在头中公开所有实现(最终会得到多个符号定义)。内联类成员函数允许您这样做。
- @vanuan:您可以在头中公开名称空间实现。只需使用inline关键字来满足odr。
- @托马斯丁不需要!= CAN
- @托马斯丁哎呀,忽略我之前的评论。不过,您仍然不能(至少对于某些编译器是默认的):stackoverflow.com/questions/3763746/&hellip;
- @vanuan:在使用inline时,只有一件事是由编译器保证的,而且它不是"内联"一个函数的主体。inline的真正(并由标准保证)目的是防止多重定义。阅读有关C++的"一个定义规则"。另外,由于预编译头问题而不是ODR问题,链接的so问题没有编译。
- 哦,如果它是在标准中定义的,那么GCC中可能有一个bug。
- "静态数据成员只是具有更好、不值得维护的声誉的全局对象。"…在许多情况下,我都非常同意,只是这过于简单化,忽略了static成员对private实例元素的特权访问。这是必要的。当然,很多人用这种方式使用static成员,但这并不是他们所能做的全部,而且他们肯定比使用全局对象更好,而且必须明确地授予friend访问权限,以满足它可能需要的一切。
- 一个缺点:不能将命名空间用作模板类型,而不是类。
我更喜欢名称空间,这样您就可以在实现文件中的匿名名称空间中拥有私有数据(这样它就不必出现在头中,而不必出现在private成员中)。另一个好处是,通过using您的命名空间,方法的客户端可以选择不指定MyMath::。
- 您也可以在带有类的实现文件中的匿名命名空间中拥有私有数据。我不确定你的逻辑。
命名空间和类方法都有它们的用途。名称空间能够跨文件分布,但是如果需要强制所有相关代码进入一个文件中,这是一个弱点。如上所述,类还允许您在类中创建私有静态成员。您可以将它放在实现文件的匿名命名空间中,但是它的作用域仍然比将它们放在类中要大。
- "[存储事物]在实现文件的匿名命名空间[是]比在类中拥有它们更大的范围"-不,不是。在不需要对成员进行特权访问的情况下,匿名名称空间的内容比private:的更私有。在许多情况下,似乎需要特权访问,这可以被考虑在内。最"private"的函数是不出现在头中的函数。private:方法永远无法享受这种好处。
使用类选项使用访问说明符的另一个原因。然后可以将公共静态方法分解为较小的私有方法。公共方法可以调用多个私有方法。
- 访问修饰符很酷,但即使是大多数private方法也比其原型根本不在头中发布的方法更容易访问(因此,仍然不可见)。我甚至没有提到匿名名称空间函数提供的更好的封装。
- 在IMO中,私有方法不如将函数本身隐藏在实现(cpp文件)中,而从不在头文件中公开。请在您的回答中详细说明这一点,以及为什么您希望使用私人成员。直到那时- 1。
- @胡说八道也许他指的是,一个有许多重复部分的庞大功能可以被安全地分解,同时将有问题的部分隐藏在私人的后面,阻止其他人在危险/需要非常小心使用时攻击它们。
- @即使如此,您也可以将这些信息隐藏在.cpp文件中的一个未命名的名称空间中,这将使它成为该翻译单元的私有信息,而不会向任何读取头文件的人提供任何多余的信息。实际上,我正试图提倡PIMPL习惯用法。
- 如果要使用模板,则不能将其放入.cpp文件中。