【问题标题】:Is a bad practice to declare static variables into functions/member functions?将静态变量声明为函数/成员函数是一种不好的做法吗?
【发布时间】:2013-03-06 16:31:03
【问题描述】:

最近一位同事给我看了这样的代码:

void SomeClass::function()
{
    static bool init = false;

    if (!init)
    {
        // hundreds of lines of ugly code
    }

    init = true;
}

他想检查SomeClass 是否已初始化,以便在每个Someclass 实例中执行一次代码,但事实是,在程序的整个生命周期中只有一个SomeClass 实例存在。

他的问题是关于init 静态变量,关于它的初始化时间。我已经回答说初始化发生了一次,所以在第一次调用时该值将是false,而在它的剩余生命周期中将是true。回答后,我补充说这种使用静态变量是不好的做法,但我无法解释原因。

到目前为止我一直在思考的原因如下:

  • static bool initSomeClass::function 的行为可以通过非静态成员变量来实现。
  • SomeClass 中的其他函数无法检查 static bool init 值,因为它的可见性仅限于 void SomeClass::function() 范围。
  • 静态变量不是OOPish,因为它们定义的是全局状态而不是对象状态。

这个原因对我来说看起来很糟糕、不聪明而且不是很具体,所以我要求更多的理由来解释为什么在函数和成员函数空间中使用静态变量是一种不好的做法。

谢谢!

【问题讨论】:

  • The behaviour of static bool init into SomeClass::function could be achieved with a non-static member variable. 怎么样?如果它是非静态的,if (!init) {} 之间的代码将在每个实例中调用一次。
  • @LuchianGrigore 我的错,我同事的代码缺少一些信息,他想检查SomeClass每个实例的初始化状态,而不是每个类检查一次,由于SomeClass 在所有程序中只有一个实例,我认为最好创建一个IsInitialized 非静态成员值。我最好编辑文本以澄清这一点。

标签: c++ static


【解决方案1】:

这肯定是一种罕见的情况,至少在高质量的代码中是这样,因为它适用于狭窄的情况。这基本上是对全局状态进行即时初始化(以提供一些全局功能)。一个典型的例子是有一个随机数生成器函数,它在第一次调用它时为生成器提供种子。另一个典型的用法是返回单例实例的函数,在第一次调用时初始化。但其他用例示例很少。

一般来说,全局状态是不可取的,并且具有包含自给自足状态的对象是首选(对于模块化等)。但是如果你需要全局状态(有时你需要),你必须以某种方式实现它。如果您需要任何类型的非平凡全局状态,那么您可能应该使用单例类,并且提供该应用程序范围的单个实例的首选方法之一是通过提供对已初始化的本地静态实例的引用的函数在第一次通话。如果需要的全局状态稍微简单一点,那么使用本地静态 bool 标志执行该方案肯定是一种可接受的方式。换句话说,我认为使用该方法没有根本问题,但如果提供这样的代码,我自然会质疑其动机(需要全局状态)。

与全局数据的情况一样,多线程会导致像这样的简单实现出现一些问题。全局状态的天真引入永远不会本质上是线程安全的,这种情况也不例外,您必须采取措施解决该特定问题。这也是全局状态不可取的部分原因。

静态 bool init 进入 SomeClass::function 的行为可以通过非静态成员变量来实现。

如果有实现相同行为的替代方案,则必须根据技术问题(如线程安全)来判断这两个替代方案。但是在这种情况下,所需的行为是有问题的,而不是实现细节,替代实现的存在并没有改变这一点。

其次,我看不出如何将全局状态的即时初始化替换为基于非静态数据成员(static 数据成员,也许)。即使可以,这也是一种浪费(需要为每个程序执行一次的东西存储每个对象),并且仅凭这一点,它不会成为更好的选择。

SomeClass 中的其他函数无法检查静态 bool init 值,因为它的可见性仅限于 void SomeClass::function() 范围。

我通常会将其放在“Pro”列中(如在 Pro/Con 中)。这是一件好事。这是信息隐藏或封装。如果你能隐藏不应该让别人关心的事情,那就太好了!但是如果有其他函数需要知道全局状态是否已经初始化,那么你可能需要更多类似于单例类的东西。

静态变量不是 OOPish,因为它们定义了全局状态而不是对象状态。

OOPish 与否,谁在乎?但是,是的,全球状态是这里的关注点。与其说是使用局部静态变量来实现其初始化。全局状态,尤其是可变的全局状态,总体上是不好的,不应该被滥用。它们阻碍了模块化(如果模块依赖于全局状态,它们的自给自足性会降低),它们引入了多线程问题,因为它们本质上是共享数据,它们使任何使用它们的函数都不可重入(非纯),它们进行调试困难,等等......名单还在继续。但是这些问题中的大多数与您如何实现它无关。另一方面,使用局部静态变量是解决静态初始化顺序失败的好方法,因此,它们是好的,因此在引入(合理的)全局状态时要担心的问题更少进入你的代码。

【讨论】:

    【解决方案2】:

    考虑多线程。当function() 可以被多个线程同时调用时,这种类型的代码是有问题的。没有锁定,你就可以接受竞争条件;使用锁定,并发可能不会获得真正的收益。

    【讨论】:

    • 本地static变量的初始化在C++11中保证是线程安全的
    • 我在点击“发布您的问题”按钮几秒钟后就意识到了这一点。大声笑
    • @AndyProwl:我不是那个意思。我的意思是两个线程可以愉快地进入if (!init) 块。
    • @AndyProwl 初始化,是的,但里面还有 if (!init)
    • @AndyProwl 初始化,是的。但是将 set 设置为 trueif() eval 非常糟糕。
    【解决方案3】:

    全局状态可能是这里最严重的问题。其他功能不必关心它,所以这不是问题。它可以在没有static 变量的情况下实现这一事实本质上意味着您制作了某种形式的单例。这当然会引入单例的所有问题,比如完全不适合多线程环境。

    【讨论】:

      【解决方案4】:

      补充其他人所说的,您不能同时拥有此类的多个对象,或者至少它们不会按预期运行。第一个实例将设置静态变量并进行初始化。后来创建的那些虽然没有自己的init 版本,但与所有其他实例共享。由于第一个实例将其设置为 true,所有后续都不会进行任何初始化,这很可能不是您想要的。

      【讨论】:

      • 我对问题的阅读表明,这正是 OP 想要的。
      • 我不确定他为什么说使用非静态变量可以实现相同的行为。如果这就是他想要的并且多线程不是问题,我不明白为什么不应该那样做。
      猜你喜欢
      • 1970-01-01
      • 2013-07-02
      • 2013-10-13
      • 1970-01-01
      • 2022-12-14
      • 1970-01-01
      • 1970-01-01
      • 2021-01-05
      • 2011-02-28
      相关资源
      最近更新 更多