最近一位同事向我展示了这样的代码:
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 init
。在回答之后,我补充说,使用静态变量是不好的做法,但我还没能解释原因。
我到目前为止所考虑的原因如下:
SomeClass::function
到SomeClass
的行为。static bool init
中的其他功能无法检查void SomeClass::function()
值,因为它的可见性仅限于{{1}}范围。这个原因看起来很糟糕,不清楚,对我来说不是很具体,所以我要求更多的理由来解释为什么在函数和成员函数空间中使用静态变量是一种不好的做法。
谢谢!
答案 0 :(得分:8)
这当然是罕见的,至少在高质量的代码中,因为它适合的狭窄情况。这基本上做的是全球状态的即时初始化(以提供一些全局功能)。一个典型的例子是具有随机数生成器功能,该功能在第一次调用时为发生器播种。另一个典型用法是返回在第一次调用时初始化的单例实例的函数。但其他用例很少见。
一般而言,全局状态是不可取的,并且具有包含自足状态的对象是优选的(对于模块化等)。但是如果你需要全局状态(有时候你需要),你必须以某种方式实现它。如果您需要任何类型的非平凡全局状态,那么您应该使用单例类,并且提供该应用程序范围单个实例的首选方法之一是通过一个函数来提供对已初始化的本地静态实例的引用在第一个电话。如果所需的全局状态稍微微不足道,那么使用本地静态bool标志执行该方案肯定是可接受的方法。换句话说,我认为使用方法没有根本问题,但如果提供此类代码,我自然会质疑其动机(需要全局状态)。
与全局数据一样,多线程会导致像这样的简单实现的一些问题。对全局状态的天真介绍永远不会具有本质上的线程安全性,并且这种情况也不例外,您必须采取措施来解决该特定问题。这也是全球各州不可取的部分原因。
可以使用非静态成员变量实现static bool init到SomeClass :: function的行为。
如果有替代方案可以实现相同的行为,那么必须根据技术问题(如线程安全性)判断两种替代方案。但在这种情况下,所需的行为是可疑的,比实现细节更多,并且替代实现的存在不会改变它。
其次,我没有看到如何用任何基于非静态数据成员(静态数据成员)替换全局状态的即时初始化,也许)。即使你可以,也会浪费(每个程序执行一次需要每个对象的存储),仅仅在这个基础上,不会使它成为更好的选择。
SomeClass中的其他函数无法检查静态bool init值,因为它的可见性仅限于void SomeClass :: function()范围。
我通常将其放在“Pro”栏中(如Pro / Con中所示)。这是一件好事。这是信息隐藏或封装。如果你能隐藏那些不应该引起他人关注的事情,那就太好了!但是如果还有其他函数需要知道全局状态已经初始化,那么你可能需要更多类似于单例类的东西。
静态变量不是OOPish,因为它们定义了一个全局状态而不是一个对象状态。
是否是OOPish,谁在乎呢?但是,全球国家是这里的关注点。与其说使用本地静态变量来实现其初始化。全球各州,特别是可变的全球国家,总的来说很糟糕,绝不应该被滥用。它们阻碍了模块化(如果模块依赖于全局状态,模块就不那么自给自足了),它们引入了多线程问题,因为它们本身就是共享数据,它们使任何使用它们的函数都是非可重入的(非纯的),它们会进行调试很难等...这个清单还在继续。但是大多数这些问题与您实施它的方式无关。另一方面,使用局部静态变量是解决静态初始化顺序惨败的好方法,因此,它们很好用,因此在引入(合理的)全局状态时要担心的问题少一个。进入你的代码。
答案 1 :(得分:4)
考虑多线程。当function()
可以由多个线程同时调用时,这种类型的代码是有问题的。没有锁定,你就会对竞争条件开放;有了锁定,并发性就会受到影响而无法获得真正的收益。
答案 2 :(得分:2)
全球状态可能是这里最严重的问题。其他功能不必关心它,所以这不是问题。事实上它可以在没有static
变量的情况下实现,这实际上意味着你制作了某种形式的单身人士。这当然会引入单身人士所有的问题,例如完全不适合多线程环境。
答案 3 :(得分:2)
添加其他人所说的,你不能同时拥有这个类的多个对象,或者至少它们不会按预期运行。第一个实例将设置静态变量并进行初始化。稍后创建的那些将没有自己的init
版本,但与所有其他实例共享。由于第一个实例将其设置为true,因此以下所有内容都不会进行任何初始化,这很可能不是您想要的。