C++:为了便于访问,在类中使用静态容器来包含指向其所有对象的指针是不是一种糟糕的做法

C++ : Is it bad practice to use a static container in a class to contain pointers to all its objects for ease of access?

本文关键字:对象 包含指 指针 是不是 一种 于访问 访问 静态 C++      更新时间:2023-10-16

我想知道在类中有一个静态容器来存储指向类对象的所有指针是否是一种糟糕的做法,这样程序的基类就可以轻松地访问它们。这是一个游戏,我在sdltutorials网站上看到了它,我觉得它非常有用。这将使我的比赛有一个非常整洁的结构,我真的不认为这样做有什么缺点,但我知道我必须小心"全球"访问,也许这会产生我现在没有看到的负面影响。

以下是上下文/示例。游戏有一个基类,其中包含Loop()、Render()、PlayAudio()、CleanMemory()等基本方法。这个想法是让单个对象在基本方法中执行相同的方法。伪代码示例:

Game::Render() {
  for (iterate all enemies in static container) {
     current_enemy::Render();
  }
}

可以肯定的是,类中的静态成员看起来是这样的:

static std::vector<Enemy*>    EnemyList;

因此,通过这种方式,当你的游戏执行基础Render()方法时,例如,你可以迭代敌人类静态容器中的所有敌人,并执行他们所有的Render(

我只想确保我意识到,如果我选择这种方法来构建我的游戏,我可能会遇到任何不利因素/复杂性/限制,因为我现在看不到任何不利因素,但我知道必须小心静态和全局的东西。

非常感谢您抽出时间。

当然很方便,但是static变量或Singleton只不过是全局变量;具有全局变量也有缺点:

  • 函数的依赖关系变得不清楚:它依赖哪个全局
  • 函数的可重入性受到损害:如果current_enemy.render()意外调用Game::Render()怎么办?这是一个无限递归
  • 除非进行适当的同步,否则函数的线程安全性会受到损害,在这种情况下,对全局变量的串行访问会阻碍并发代码的性能(因为Amdahl定律)

在任何需要的地方显式传递对Game实例的引用可能看起来很痛苦,也毫无意义,但它留下了一条可以遵循的依赖关系的清晰路径,随着软件的发展,您会欣赏的明确性

当然,关于将程序转换为具有两个Game实例,还有很多要说的。虽然在这种确切的情况下,这可能看起来不协调,但总的来说,明智的做法是不要认为这在未来永远没有必要,因为我们不是先知。

不同的人对此可能有不同的看法。我可以给你一些关于如何以更好的方式存储静态对象的建议。

对存储对象的类使用singleton模式:

class ObjectManager
{
private:
   std::vector<Enemy*> enemies_;
   std::vector<Friend*> friends_;
   ...
public:
   void add(Enemy* e) { enemies_.push_back(e); }
   ...
   const std::vector<Enemy*> enemies() const { return enmies_; }
   ...
private:
   static ObjectManager* instance_;
public:
   static ObjectManager* Get() { return instance_; }
   static void Initialize() { instance_ = new ObjectManager(); }
}

你可以这样访问它(例如C++11基于范围):

void Game::Render() {
    for(auto e : ObjectManager::Get()->enemies()) {
        e->Render();
    }
}

这对于想要访问世界信息的子类来说尤其方便。通常,您必须为每个人提供一个指向ObjectManager的指针。但是,如果您只有一个ObjectManager,那么单例模式可能会从代码中去除混乱。

不要忘记在程序开始时通过调用ObjectManager::Initialize();来创建singleton。

我不建议你这样做。在这一点上,您还可以在名称空间中有一个裸露的全局变量,这与您现在正在做的事情相同。

我也不建议使用单身汉。

什么时候不应该使用Singleton模式?(除了显而易见的)

最好的方法是尽可能地进行良好的旧参数传递(依赖项注入)。经过精心设计,这在全系统范围内是可行的,它避免了全球可访问资源的所有问题。

当你没有以这种方式设计系统的奢侈,并且你在现有的代码中工作,这些代码已经有了相当多的单例依赖性问题,或者在资源之间失去了位置性(从需要它们的地方删除了几个级别)(而且你无法修改接口以向下级联依赖性),这可能不是有用的建议。

裸全局和单例之间的中间地带是服务定位器。许多人仍然认为服务定位器是一种反模式,但大多数人也认为它没有单例那么糟糕,因为它提供了一定级别的抽象,并将创建与提供对象解耦,这意味着如果您的设计或环境发生变化,您可以轻松地提供派生类。

以下是对模式的描述:

http://gameprogrammingpatterns.com/service-locator.html

下面是关于singleton与服务定位器的讨论。

如果辛格尔顿不好,那么为什么服务容器好呢?。

我最喜欢投票最高(但未被接受)的答案。