将静态变量声明为函数/成员函数是一种不好的做法
Is a bad practice to declare static variables into functions/member functions?
最近一个同事给我看了这样一个代码:
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()
范围。 静态变量不是OOPish,因为它们定义了全局状态而不是对象状态。
这个理由看起来很糟糕,不聪明,对我来说不是很具体,所以我要求更多的理由来解释为什么在函数和成员函数空间中使用静态变量是一种不好的做法。
谢谢!
这至少在高质量的代码中是很少发生的,因为它只适用于少数情况。这基本上是对全局状态进行及时初始化(以提供一些全局功能)。这方面的一个典型例子是拥有一个随机数生成器函数,该函数在第一次调用它时为生成器提供种子。它的另一个典型用法是返回单例实例的函数,该实例在第一次调用时初始化。但是其他的用例却很少。
一般来说,全局状态是不可取的,拥有包含自给自足状态的对象是首选(为了模块化等)。但是如果您需要全局状态(有时确实需要),则必须以某种方式实现它。如果您需要任何一种重要的全局状态,那么您可能应该使用单例类,而交付应用程序范围内的单实例的首选方法之一是通过一个函数,该函数提供对在第一次调用时初始化的本地静态实例的引用。如果所需的全局状态比较琐碎,那么使用本地静态bool标志来执行该方案当然是一种可接受的方法。换句话说,我认为使用方法没有根本问题,但是如果用这样的代码呈现,我自然会质疑它的动机(需要全局状态)。
对于全局数据来说,多线程总是会导致像这样的简单实现出现一些问题。简单地引入全局状态永远不会是线程安全的,这种情况也不例外,您必须采取措施来解决特定的问题。这就是全球国家不可取的部分原因。
静态bool init到someeclass::函数的行为可以通过一个非静态成员变量来实现。
如果有实现相同行为的替代方案,则必须根据技术问题(如线程安全)对这两个替代方案进行判断。但是在这种情况下,所需的行为是有问题的,比实现细节更有问题,并且存在替代实现并没有改变这一点。
第二,我不知道如何用基于非静态数据成员(可能是静态数据成员)的任何东西来替换全局状态的即时初始化。即使可以,它也会很浪费(对于每个程序执行一次的事情需要每个对象存储),并且仅从这一点来看,它不会是一个更好的选择。someeclass中的其他函数无法检查静态bool初始值,因为它的可见性仅限于void someeclass::function()范围。
我通常会把它放在"赞成"一栏(如赞成/反对)。这是一件好事。这就是信息隐藏或封装。如果你能把别人不应该关心的事情藏起来,那就太好了!但是,如果有其他函数需要知道全局状态是否已经初始化,那么您可能需要更类似于单例类的东西。
静态变量不是oop,因为它们定义的是全局状态而不是对象状态。
不管你喜不喜欢,谁在乎呢?但是,是的,全球状态是这里关注的问题。而不是使用局部静态变量来实现其初始化。全局状态,尤其是可变的全局状态,通常是不好的,不应该被滥用。它们阻碍了模块化(如果依赖全局状态,模块就不那么自给自足),它们引入了多线程问题,因为它们本质上是共享数据,它们使任何使用它们的函数不可重入(非纯),它们使调试变得困难,等等……这样的例子不胜枚举。但这些问题大多与你如何实现它无关。另一方面,使用局部静态变量是解决static-initialization-order-惨败的好方法,因此,它们是好的,在向代码中引入(合理的)全局状态时,可以少担心一个问题。
考虑多线程。当function()
可以被多个线程并发调用时,这种类型的代码是有问题的。没有锁,就会出现竞争条件;使用锁,并发性可能会受到损害,而没有实际的好处。
全局状态可能是这里最严重的问题。其他函数不需要关心它,所以这不是问题。事实上,没有static
变量也可以实现,这实际上意味着您创建了某种形式的单例。这当然会带来单例模式的所有问题,比如完全不适合多线程环境。
加上其他人所说的,您不能同时拥有该类的多个对象,或者至少它们不会像预期的那样运行。第一个实例将设置静态变量并进行初始化。后来创建的实例虽然没有自己的init
版本,但与所有其他实例共享它。由于第一个实例将其设置为true,因此下面的所有内容都不会进行任何初始化,这很可能不是您想要的。
- 有一个打印语句的函数是一种糟糕的编程实践吗
- 有没有一种代码密度较低的方法来使用非默认构造函数初始化数组?
- C++ STD 函数运算符:有没有一种方法可以通过函数将一个向量映射到另一个向量上?
- 如果 C 函数仍然可以间接执行(通过回调函数),那么将它声明为静态函数是否是一种不好的做法?
- 构造函数是否有一种现代C++方法来了解其'container'类?
- 有没有一种简单的方法可以在对象向量上调用构造函数?
- 有没有一种简单的方法来调用带有默认参数的函数?
- 有没有一种方法可以从函数中返回一个新对象或对现有对象的引用
- 在C++中,有没有一种方法可以让我在不传递参数的情况下拥有一个函数
- 有没有一种方法可以在linux中扫描已构建的ARM库中的函数
- 有没有一种方法可以获得传递给函数的数组的大小
- 常量静态成员函数:有另一种方法可用吗?
- C++11 中的随机数:有没有一种简单的方法可以将生成器种子放在代码的一个位置,然后在不同的函数中使用它?
- 是否有一种方法可以调用一个函数,而不会创建变量,而不会创建变量
- 有没有一种单行方法来调用集合上的 lambda 函数
- 是否有一种非间接、非黑客的方式来保证 constexpr 函数仅在编译时可调用?
- 是否有一种方法可以在超载运算符函数中接触默认运算符函数
- 是否有一种方法可以避免标头文件中使用的constexpr函数输入全局范围,而无需额外的名称空间
- 在函数中使用布尔"recursiveCall"参数是一种好的做法吗?
- 一种类型特征,标识哪个类提供通过重载解析选择的函数