只有静态成员的实用程序类是C ++中的反模式吗?


39

我应该在何处放置与类无关的函数这个问题引发了一些争论,即在C ++中将实用程序函数组合到类中是否有意义或者只是将它们作为自由函数存在于命名空间中。

我来自C#背景,后者不存在该选项,因此自然倾向于在我编写的C ++小代码中使用静态类。该问题的最高投票答案以及一些评论都说,自由函数是首选,甚至暗示静态类是反模式。为什么在C ++中会这样?至少从表面上看,类上的静态方法似乎与命名空间中的自由函数没有区别。为什么要选择后者呢?

如果实用程序功能的集合需要一些共享数据,例如可以将其存储在私有静态字段中的缓存,情况会有所不同吗?


听起来有点像“功能分解”反模式。
user281377 '02

7
简短的答案:您只需要一个类即可包装这些函数。自由函数比将它们压缩到某种伪OO构造中更适合您的任务,这是仅在“ purely-OO”语言中才需要的解决方法。
克里斯说,请恢复莫妮卡(Monica)2012年

Answers:


39

我想回答一下,我们应该比较类和名称空间的意图。根据维基百科:

在面向对象的程序设计中,类是一种构造,用作创建自身实例的蓝图-称为类实例,类对象,实例对象或简称为对象。一个类定义了使这些类实例具有状态和行为的组成成员。数据字段成员(成员变量或实例变量)使类对象可以维护状态。其他类型的成员,尤其是方法,可以实现类对象的行为。类实例是相关类的类型。

命名空间

通常,名称空间是一个容器,它为其所拥有的标识符(名称,技术术语或单词)提供上下文,并允许对驻留在不同名称空间中的同音异义标识符进行消歧。

现在,您想通过将函数放入类(静态)或命名空间来实现什么?我敢打赌,命名空间的定义可以更好地描述您的意图-您所需要的只是功能的容器。您不需要类定义中描述的任何功能。请注意,类定义的第一个单词是“ 在面向对象程序设计中 ”,但是关于函数集合,没有面向对象的东西。

也可能有技术上的原因,但是当有人来自Java并试图使我了解C ++的多范式语言时,对我来说最明显的答案是:因为我们不需要OO来实现这一目标。


1
+1表示“我们不需要OO来实现这一目标”
Ixrec

我是OO的狂热者,对此表示同意,但是我想在某些情况下,使用全静态类是唯一的解决方案,例如替换命名空间,因为您不能在其中声明嵌套的命名空间一类。
Wael Boutglay '18年

26

在称呼它为反模式时,我会非常谨慎。通常首选名称空间,但是由于没有名称空间模板,并且不能将名称空间作为模板参数进行传递,因此仅使用静态成员的类非常普遍。


3
OP的最后一行掩盖了其他答案。命名空间几乎是命名范围,因此,如果需要访问控制,则类是唯一可用的工具。
cmannett85 '02

2
@ cbamber85是公平的,我只有阅读了前两个答案后才将其插入。
PersonalNexus

@PersonalNexus嗯,很公平,我没有意识到!
cmannett85 '02

10
@ cbamber85:并非完全如此。通过在实现文件中放置“私有”功能,您将获得更好的封装,因此用户甚至看不到私有声明。
UncleBens 2012年

2
+1模板确实是名称空间仍缺乏功能的地方。
克里斯说,请恢复莫妮卡(Monica)2012年

13

过去,我需要参加FORTRAN课程。那时我已经知道其他命令式语言,因此我认为我可以不花太多时间就能做FORTRAN。当我上交第一份作业时,教授将其退还给我并要求重做:他说用FORTRAN语法编写的Pascal程序不算作有效的提交。

这里也存在类似的问题:使用静态类托管C ++中的实用程序函数是C ++的一个外来惯用语。

正如您在问题中提到的那样,在C#中为实用程序函数使用静态类是必须的:独立功能不是C#中的一种选择。开发一种模式所需的语言,允许程序员以其他方式(即在静态实用程序类中)定义独立功能。这是逐字逐句地采取的Java技巧:例如,.NET的java.lang.MathSystem.Math几乎是同构的。

但是,C ++提供了名称空间,这是实现同一目标的另一种功能,并且在其标准库的实现中积极使用它。添加额外的静态类层不仅是不必要的,而且对于没有C#或Java背景的读者也有些违反直觉。从某种意义上说,您正在为该语言引入“贷款翻译”,以便可以本地表达某些内容。

当您的职能需要共享数据时,情况就不同了。因为您的功能不再无关,所以Singleton模式成为解决此要求的首选方法。


6
+1,但推荐单身人士除外。它们不是 C ++的首选!如果函数需要共享状态,则它们可以位于类中,但是除非确实需要,否则类不应为单例。而且他们通常不这样做。
2012年

@Maxpm别误会,单例仅首选全局静态变量。除此之外,没有什么是“首选”的。
dasblinkenlight 2012年

2
有一篇很好的文章,《单例:解决自1995年以来从未遇到过的问题》。概括地说,将众所周知的实例耦合到类本身上不必要地限制了您的灵活性,却无济于事。大多数时候,只有一个类并且在某个地方有一个共享的实例,但是不要使其成为单例。
2012年

我不确定“外来习语”是否正确。这是一个非常常见的成语。我认为相当多的编程团队正在使用Stroustrup的e2 ...
Nick Keighley

10

也许您需要问为什么要一个全静态的类?

我能想到的唯一原因是,“一切都是一类”的其他语言(Java和C#)也需要它们。这些语言根本无法创建顶级函数,因此他们发明了一种保留它们的技巧,那就是静态成员函数。它们有点变通,但是C ++不需要这样的东西,您可以直接创建全新的顶级独立函数。

如果您需要对特定数据项进行操作的函数,则有必要将它们捆绑到一个保存数据的类中,但是随后,这些函数将不再是函数,而是开始成为对该类的数据进行操作的该类的成员。

如果您有一个不能对特定数据类型进行操作的函数(我在这里用这个词是因为类是定义新数据类型的方法),那么将它们推到类中确实是一种反模式,无非就是语义目的。


10
确实-我会说Java中的“具有所有静态成员的类”实际上是一种规避 Java局限性的技巧
查尔斯·索尔维亚

@CharlesSalvia:我很礼貌:)我对main()也属于Java类也有同样的不好的感觉,尽管我知道他们为什么这样做。
gbjbaanb 2012年

实际上,这似乎为为什么您想要一个全静态的类提供了答案。还是我误会你了?例如,我需要保存一个变量,该变量的值可以更改,该变量可以由一个类读取(我打算存储在ROM中),并由另一类写入(该变量将在RAM中)。仅仅将其放置在已知的ram位置是不够的-将其(及其访问器)包装在一个类中可以让我微调访问控制。您在第二段中所描述的几乎是什么。
iheanyi 2014年

5

至少从表面上看,类上的静态方法似乎与命名空间中的自由函数没有区别。

换句话说,使用类而不是名称空间没有优势。

为什么要选择后者呢?

一方面,它可以节省您一直输入static的时间,尽管这可以说是一个很小的好处。

主要好处是,它是工作中功能最弱的工具。类可用于创建对象,它们可用作变量的类型或模板参数。这些都不是您想要的功能集合所需要的功能。因此,最好使用不具有这些功能的工具,以免意外使用该类。

使用命名空间之后,还可以立即向代码的所有用户清楚这是函数的集合,而不是从中创建对象的蓝图。


5

...但是,有几条评论说自由函数是首选,甚至暗示静态类是反模式。为什么在C ++中会这样?至少从表面上看,类上的静态方法似乎与命名空间中的自由函数没有区别。为什么要选择后者呢?

全静态课程可以完成工作,但是就像四门轿车可以做到时,驾驶一辆53英尺的半卡车到杂货店买薯片和莎莎酱(即,过大了)。类带有少量额外的开销,它们的存在可能给人以实例化一个实例的印象。C ++(和C,其中所有函数都是免费的)提供的免费函数不会这样做;它们只是功能而已。

如果实用程序功能的集合需要一些共享数据,例如可以将其存储在私有静态字段中的缓存,情况会有所不同吗?

并不是的。staticC语言(和更高版本的C ++)的最初目的是提供可在任何级别的范围内使用的持久性存储:

int counter() {
    static int value = 0;
    value++;
}

您可以将范围扩展到文件级别,这使它对所有较窄的范围可见,但不在文件外部:

static int value = 0;

int up() { value++; }
int down() { value--; }

C ++中的私有静态类成员具有相同的目的,但仅限于类的范围。counter()上面的示例中使用的技术也可以在C ++方法中使用,并且如果变量不需要在整个类中可见,我实际上建议这样做。


我认为一组不共享数据的不相关的实用程序函数应该更好地分组在命名空间中。但是,如果您有一组紧密耦合的实用程序函数需要访问共享数据以及某些访问控制,则静态类是最佳选择。函数内的静态变量是不可重入的。在这种情况下,使用全局模块...稍微好一点,但是如果您有我描述的要求,我仍然认为全静态类是最好的解决方案。
iheanyi 2014年

如果它们共享数据,它们是否可以作为没有静态方法的类更好?
尼克·基利

@NickKeighley这取决于谁赢得了关于创建一个实例并将其传递给需要它的所有对象还是使其静态化的更好的辩论。
Blrfl

1

如果一个函数不保持任何状态并且是可重入的,则将其推入类似乎没有什么意义(除非受到语言的强制)。如果函数保持某种状态(例如,只能通过静态互斥锁使其成为线程安全的),则类上的静态方法似乎是合适的。


1

尽管有关C ++的某些基本知识可以使es / s namespacesclasses / structs截然不同,但此处有关该主题的大部分论述还是有意义的。

静态类(所有成员都是静态的类,并且永远不会实例化的类)本身就是对象。它们不只是namespace包含功能的一个。

模板元编程允许我们使用静态类作为编译时对象。

考虑一下:

template<typename allocator_type> class allocator
{
public:
    inline static void* allocate(size_t size)
    {
        return allocator_type::template allocate(size);
    }
    inline static void release(void* p)
    {
        allocator_type::template release(p);
    }
};

要使用它,我们需要包含在类中的函数。名称空间在这里不起作用。考虑:

class mallocator
{
    inline static void* allocate(size_t size)
    {
        return std::malloc(size);
    }
    inline static void release(void* p)
    {
        return std::free(p);
    }
};

现在使用它:

using my_allocator = allocator<mallocator>;

void* p = my_allocator::allocate(1024);
...
my_allocator::release(p);

只要新的分配器公开兼容的allocateand release函数,切换到新的分配器就很容易。

这是无法通过名称空间实现的。

您是否总是需要函数才能成为课程的一部分?没有。

使用静态类是反模式吗?这取决于上下文。

如果实用程序功能的集合需要一些共享数据,例如可以将其存储在私有静态字段中的缓存,情况会有所不同吗?

在这种情况下,您试图实现的目标可能最好通过面向对象的编程来实现。


这里使用inline关键字是多余的,因为在类定义中定义的方法是隐式内联的。请参阅en.cppreference.com/w/cpp/language/inline
特雷弗

1

正如约翰·卡马克(John Carmack)所说:

“有时候,优雅的实现只是一个函数。不是方法。不是类。不是框架。只是函数。”

我认为几乎可以总结一下。如果显然不是一堂课,为什么还要强行将其设为一堂课?C ++具有您可以实际使用函数的优点。在Java中,一切都是方法。然后,您需要实用程序类,并且其中有很多。

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.