代码之家  ›  专栏  ›  技术社区  ›  Baruch

全局变量(再次)

  •  2
  • Baruch  · 技术社区  · 14 年前

    我一直听说全球变量应该 被利用,但我有一种倾向 “规则就像头脑发热。真的没有例外吗?
    例如,我目前正在用c++和SDL编写一个小游戏。在我看来,有一个指向屏幕缓冲区的指针的全局变量是很有意义的,因为所有代表游戏中不同类型的东西的不同类都需要blit到它,并且只有一个屏幕缓冲区。

    如果我是对的,请告诉我有例外情况,如果没有,请告诉我:

    • 为什么不呢? 不惜一切代价 (请解释一下)
    • 如何才能做到这一点 ,最好不必在需要之前将其传递给要在内部存储的每个构造函数,或传递给对paint()请求的每个调用。

    (我想这个问题以前是这样问的,但是在搜索时找不到我需要的(解释和解决方法)。如果有人能发上一个问题的链接,那就太好了)

    11 回复  |  直到 14 年前
        1
  •  7
  •   Stack Overflow is garbage    14 年前

    当然也有例外。我个人无法想象有哪种情况下goto是正确的解决方案(或者singleton是正确的解决方案),但是全局变量偶尔也有它们的用途。但是。。。你找不到有效的借口。

    ,重复, 需要访问屏幕缓冲区。这是渲染器和 否则。你不希望你的记录器,输入管理器,人工智能或其他任何人把随机垃圾放在屏幕上。

    这就是为什么人们说“不要使用globals”。这不是因为地球人是某种终极邪恶,而是因为如果我们 不要 这么说吧,人们会落入你的陷阱,“是的,但这条规则不适用于 ,对吧?我 所有能接触到X的东西。不,你需要学会组织你的程序。

    更常见的例外是无状态对象或静态对象(如记录器),或者可能是应用程序的配置:只读或只写的,并且真正需要从 到处 存在

    简而言之,globals的问题在于它们违反了封装: 依赖于全局的代码可重用性较差。我可以把你正在使用的同一个类放在我的应用程序中,它就会崩溃。因为我没有它所依赖的全局对象网络。

    f(x) 回来吗? x 是。但如果我也通过了 两次,我会得到同样的结果吗?如果它使用了大量的globals,那么可能不会。然后很难弄清楚它将返回什么,以及 这就行了。它是否会设置一些全局变量来影响其他看似无关的函数?

    如何实现这一点,最好不必将其传递给每个构造函数,以便在需要之前在内部存储

    你让人觉得那是件坏事。如果一个对象需要知道屏幕缓冲区,那么您应该 屏幕缓冲区。要么在构造函数中,要么在以后的调用中。(它有一个很好的奖励:它 如果你的设计是草率的。如果有500个类需要使用屏幕缓冲区,则必须将其传递给500个构造函数。这很痛苦,所以这是一个警钟: )

    cos(1.42)

    我们通常就是这样做的,没有全球性的。当然,我们可以说“是的,但是每个人都需要能够把论点 cos ,我最好让它全球化”。然后它会像这样:

    gVal = 1.42;
    cos();
    

    我不知道你的情况,但我觉得第一个版本更可读。

        2
  •  8
  •   JOTN    14 年前

    我们告诉学生不要使用全局变量,因为它鼓励更好的编程方法。这和我们告诉他们不要使用goto语句的原因是一样的。一旦你是一个成功的程序员,你就可以打破规则,因为你应该知道什么时候合适。

        3
  •  7
  •   Zack Bloom    14 年前

    与其他任何设计决策一样,使用全局变量也有代价。它省去了不必要地传递变量,并允许您在运行的函数之间共享状态。但它也有可能使代码难以遵循和重用。

    在由异构组件组成的大型系统中,使用globals可能会成为维护的噩梦。在某些情况下,您可能需要具有不同属性的不同屏幕缓冲区,或者屏幕缓冲区在初始化之前可能不可用,这意味着您必须用检查它是否为空来包装对它的每个调用,或者您需要编写多线程代码,全局将需要一个锁。

    简而言之,当您的应用程序小到可以管理时,您可以自由使用全局变量。当它开始增长时,它们将成为一种负担,要么需要重构来删除,要么将削弱程序的增长(在能力或稳定性方面)。不要使用它们的警告源自多年的经验教训,而不是程序员的“头脑发热”。

        4
  •  3
  •   Puppy    14 年前

    如果你想更新你的引擎来支持双屏呢?多重显示正变得越来越普遍。或者如果你想引入线程呢?砰。如果你想支持多个渲染子系统呢?哇哦。我想把我的代码打包成一个库,让别人或者我自己重新使用?废话。

    另一个问题是源文件之间全局init的顺序是未定义的,因此很难维护多个。

    最终,您应该只有一个对象可以使用屏幕缓冲区——呈现对象。因此,屏幕缓冲区指针应该是该对象的一部分。

    我同意你的观点,从根本上来说,“从不”是不准确的。您所做的每个函数调用都是调用一个全局变量—该函数的地址。对于导入的函数(如OS函数)尤其如此。还有一些事情你根本无法理解,即使你想-像堆。然而,这无疑不是使用全局的正确位置。

    全球化的最大问题是,如果你后来决定一个全球性的事情不是出于任何原因去做的(而且有很多原因),那么他们就绝对是地狱了。一个简单的事实是,使用一个全局只是不思考。我懒得设计一个真正的渲染子系统和对象,所以我要把这些东西放到一个全局中。这很容易,很简单,不这样做是软件编程史上最大的革命,而且是有充分理由的。

    制作一个渲染类。把指针放进去。使用成员函数。问题解决了。

    编辑:我重新读了你的评论。问题是你已经分担了你的责任。每个类(位图、文本等)都不应该呈现自己。它应该只保存主渲染对象渲染它所需的数据。位图的工作是表示位图,而不是渲染位图。

        5
  •  2
  •   Leonid    14 年前
    • 全局变量可以以意外的方式更改,这通常不是您想要的。应用程序的状态将变得复杂且无法维护。很容易出错。尤其是如果有人在修改你的代码;

    • 辛格尔顿可能是个更好的主意。这至少会给你一些封装,以防你将来需要做扩展。

    • 我们经常在工作中使用全局(到命名空间)常量,这被认为是正常的,因为它们不会改变(以出乎意料的方式),而且在多个文件中使用它们非常方便。

        6
  •  2
  •   Steve Jessop    14 年前

    如果屏幕缓冲区在许多不同的代码段之间共享,则有两个选项:

    1) 到处传。这是不方便的,因为使用屏幕缓冲区的每一段代码,甚至是间接的,都需要通过这个对象通过调用堆栈的事实来表示。

    2) 使用全局。如果你这么做,那就你所知道的 整个程序中的任何函数 可能会使用屏幕缓冲区,只需从全局[*]中获取它。因此,如果需要对屏幕缓冲区的状态进行推理,则需要在推理中包含整个程序。如果有某种方法可以指出哪些函数可以修改屏幕缓冲区,而哪些函数不可能这样做的话。哦,等一下。。。

    这甚至不包括依赖注入的好处——在测试时,以及在程序的未来迭代中,对于某些函数的调用者来说,可能是有用的 哪里 它应该指向屏幕,而不一定是屏幕。

    同样的问题也同样适用于单子,正如它们适用于其他可修改的全局变量一样。

    你甚至可以证明 应该 应该 尝试编写松散耦合的系统,这样做自然会导致很少的代码需要知道屏幕的任何信息才能完成工作(即使他们知道自己在操作图像,他们也不必关心这些图像是在屏幕缓冲区中,还是在一些后缓冲区中,或者一些与屏幕无关的缓冲区)。实际上,我并不赞成做额外的工作来惩罚自己编写更好的代码,但是globals确实让我很容易在我的应用程序中添加另一个不适当的耦合。

    [*]好吧,您可以缩小范围,因为只有包含相关头文件的TUs才有声明。从技术上来说,没有什么可以阻止他们复制和粘贴它,但是在一个完全规范的代码库中,他们不会。

        7
  •  1
  •   Mike DeSimone    14 年前

    我从不这样做的原因是因为它造成了混乱。假设将所有唯一变量设置为globals,您将拥有一个电话簿大小的外部列表。

    if (global_var = 0) // uh oh :-(
    if (object->Instance() = 0) // compile error :-)
    

    这两个问题都可以用单子来解决。你不能给返回你对象地址的函数赋值。

    除此之外:您不需要在应用程序中的任何地方都使用屏幕缓冲区,但是如果您想:继续,它不会使程序运行得不太好:-)

        8
  •  1
  •   Cheers and hth. - Alf    14 年前

    “为什么不”:全局变量为您提供意大利面信息流。

    这和goto给你的意大利面控制流是一样的。

    come from 声明虽然最初希望最终确定控制权来自何方,但结果并没有真正解决 goto 。类似地,用于跟踪全局变量更新的更现代的语言特性,如 onchangeby

    干杯。,

        9
  •  0
  •   Community CDub    8 年前

    全局变量(和单变量,它们只是全局变量的包装器)可能会导致许多问题,如我在 this answer

    为了这个 具体的 问题——游戏工具包中的可移动对象——我倾向于建议一个方法签名,比如 Sprite::drawOn(Canvas&, const Point&) . 传递对画布的引用不应该是过多的开销,因为除了在绘制路径中之外,不太可能需要它,而且在该路径中,您可能无论如何都在迭代集合,所以在该循环中传递它并没有那么困难。通过这样做,您可以隐藏主程序只有一个来自sprite类的活动屏幕缓冲区,因此使它不太可能依赖于这个事实。

        10
  •  0
  •   Clifford    14 年前

    在这种情况下,为所有需要访问屏幕缓冲区的方法提供成员函数的类将是一种更友好的OOP方法。为什么每个人都可以不受控制地使用它!?

    Here's a good read on the subject (与嵌入式编程相关,但要点适用于任何代码,只是有些嵌入式程序员认为他们有正当的理由)。

        11
  •  -2
  •   suszterpatt    14 年前

    我也很想听到确切的解释,但我可以告诉你,Singleton模式通常很好地填补了全局变量的角色。