【问题标题】:C++ - Are classes with only static members bad practice/anti-pattern?C++ - 只有静态成员的类是不好的做法/反模式吗?
【发布时间】:2022-01-18 05:16:16
【问题描述】:

关于只包含静态成员的类,我看到很多人声称这是一种糟糕的模式,而且它从来都不是问题的最佳解决方案。

Are utility classes with nothing but static members an anti-pattern in C++? 上被接受且票数最高的答案主张使用命名空间,并以声明结束

对我来说最明显的答案是:因为我们不需要 OO 来实现这一点。

这对我来说很奇怪,有几个原因。似乎他们在谈论只包含静态成员函数的类,而不是包含静态数据和静态函数混合的类。仅具有静态成员函数的类确实可以替换为命名空间内的全局函数,但是如何替换包含静态函数和静态数据的类呢?

struct Foo
{
    static void add5()
    {
        s_x += 5;
    }
    static const int& getX()
    {
        return s_x;
    }
private:
    static int s_x;
};
int Foo::s_x{ 0 };

在命名空间中有函数和全局变量?你如何确保s_x 不能被外部代码直接访问和修改?

让我感到困惑的另一件事是,我觉得只有静态成员的类在 C++ 书籍和库中相当普遍。

SFMLsf::Mouse 只有静态成员。

Game Programming Patterns 多次使用只有静态成员的类,甚至在Singleton chapter 中使用术语“静态类”来指代它。而Service Locator chapter 本质上是一个只有静态成员的类。

那么人们应该怎么想呢?只有静态成员的类是不好的设计/不好的做法吗?或者他们在某些情况下有自己的位置?

【问题讨论】:

  • 你可以找到几个糟糕设计的例子并不意味着它是好的。 :-) 您可以将命名空间数据放在 cpp 文件中,并且只公开函数。您不必在标题中包含它。
  • 我不会做出如此笼统的“反模式”声明。像 C++ 的许多方面一样,它可能会被误用或很好地使用。虽然命名空间可能相似,但您的 struct Foo 也是一种类型,这意味着它可以在类型上下文中使用,例如模板参数。
  • 我也不会将其描述为“反模式”。然而,有很多文章描述了为什么它通常更好——从设计或编码的角度——避免静态分配或“全局”数据——无论是未命名命名空间中的数据还是类的静态成员。但是,就像任何编程语言的任何特性一样,在某些情况下,静态分配的数据是解决问题的合适甚至更可取的解决方案,而在其他情况下,另一种选择会更好。

标签: c++ class static


【解决方案1】:

另一个问题的最佳答案仍然成立:您不需要类来封装静态函数和静态变量。这可以通过命名空间来完成,并且——通过一些纪律——使用编译单元的范围。

在您的示例中,如果您使用命名空间,则无需公开私有静态数据成员的存在:

在标题中声明:

namespace Foo {
    void add5();
    const int& getX();
}

然后在cpp中实现:

namespace Foo
{
    static int s_x{0};   // static globals are not visible outside the compilation unit

    void add5()
    {
        s_x += 5;
    }
    const int& getX()
    {
        return s_x;
    }
};

注意:静态全局变量与静态成员并不完全相同,但在上面的代码中,它的效果与私有成员相同。对于公共数据成员,您只需删除关键字static

【讨论】:

  • 那么对于经常使用“静态类”的东西,您是否建议完全放弃该类并使用具有静态全局变量和全局范围函数的命名空间?就像游戏的Engine 类或AudioSystem 类一样,它只会有一个实例,并且会在代码中的许多地方使用。如果您正在编写游戏,您会放弃这些类并将全局变量和函数放入 audioengine-namespaces 中吗?或者你会为这些编写普通类,然后在头文件中声明它们的全局静态实例?
  • @JensB 我的信息不是不惜一切代价获得类的装备,而是仅在封装有意义时才使用类(参见我对另一个答案的政策/策略模式的评论)。 C++ 是多范式的,如果您只有一个引擎和一个具有全局状态的音频系统(因为这是静态数据成员),那么您没有使用类的优势,并且您将在命名空间方面拥有更大的灵活性(例如组合命名空间或在不同版本之间进行选择)。
  • @JensB 我不在游戏行业,所以我无法帮助你。但在我的书架上,我有 Mike McShaffry 的“Game Coding Complete”。这本书真的很有趣。他知道游戏。所以我在关于物理引擎的页面上打开它:他使用了一个带有虚函数和普通成员的真实类。这允许多态性和运行时配置,重用游戏特定的专业化,许多只有静态成员的假类你不会拥有的东西。顺便说一句,如果你有不属于一起的函数,将它们人为地打包在一个类中会违反 SRP。
【解决方案2】:

虽然我通常也推荐命名空间,但在某些情况下,类显然是更好的方法。例如,您可以将类作为模板参数传递,这意味着您可以让策略类以不同的方式实现接口,并“插入”。命名空间无法做到这一点。

但如果您不打算以这种方式引用名称组,我不建议将其作为组织代码的一般方法。

【讨论】:

  • 这是一个有效的观点。在某些情况下,只有静态成员的 C++ 类是有意义的,而模板就是一个很好的例子。然而,如果有几个替代类(即策略模式、策略驱动设计),则主要是相关的。我的回答主要是针对实用函数的一般情况,特别是如果它们并不真正相关。在这种情况下,命名空间甚至具有使用using 语句促进版本控制和命名空间组合的优势。
  • @Christophe:有人可能会说“静态类”不会因滥用 using namespace XXX; 而受到影响;-)
猜你喜欢
  • 2010-12-28
  • 2011-02-14
  • 2011-09-22
  • 1970-01-01
  • 2013-04-25
  • 2013-05-23
  • 2011-07-07
  • 2013-05-11
相关资源
最近更新 更多