很难找到任何理由去硬生生地声明全局变量。 全局变量阻碍了代码重用,而且使代码变得
更难维护。阻碍重用是因为任何用了全局变量的代码就立刻与之耦合,这使得全局变量一改
它们也飞得跟着改,从而使任何重用都不可能了。它们使代码变得更难维护的原因是很难
分辨出哪些代码用了某个特定的全局变量,因为任何代码都有访问它们的权限。
全局变量增加了模块间的耦合,因为它们往往作为幼稚的模块间消息传递机制的设施存在。
就算它们能单词重任,从实践角度来说,要从大型软件的源代码去掉任何全局变量都几乎不
可能。这还假定它们能正常工作的情况。不过可不要忘了,全局变量是不设防的。随便哪个
维护你代码的C++新手,都能使对全局变量有强烈依赖的软件随时崩溃。
全局变量的辩护者们经常拿它的“方便”来说事。这真是自私自利之徒的无耻之争。要知道
软件的维护常常比它的初次开发要花费更多时间,而使用全局变量就意味着把烂摊子扔给
了维护工程师。假设我们有一个系统,它有一个全局可访问的"环境",并且(我们按需求保证)
确实只有“一个”。不幸的是,我们选择了使用全局变量(来表示它):
extern Environment * const theEnv;
在软件就要交付之前,我们会发现,可能同时存在的环境要增加到两个、三个,或是在系统
启动时指定的,或根本就是完全动态的某个数。这种在软件发布的最后时刻发生的变更实属
家常便饭。在备有无微不至的源代码控制过程的大项目里,这个变更会引发极费时间、涉及
所有源文件的更改,即使在最细小的和最直截了当的那些地方也不例外。整个过程预计要几天
到几星期不等。假如我们不用全局变量,只要五分钟我们就能搞定这一切:
Environment *theEnv();
仅仅是把对于值的访问加上了函数形式的包装,我们就获得了可贵的可扩充性。要是再
加上函数重载,或是给予函数形式参数以默认值,我们就根本不需要怎么改源代码了。
Environment *theEnv( EnvCode whichEnv = OFFICIAL);
另一个全局变量引起的问题并不能一望而知。此问题的来源是全局变量经常要求(延迟到)
运行期(才进行的)静态初始化,在C++语言中,如果静态变量用来初始化的值不能在编译
时就计算妥当,那么这个初始化的动作就会被拖到运行期。这是许多致命后果的始作俑者
extern Environment * const theEnv = new OfficalEnv;
如果改用函数或class来充当访问全局信息的东西,初始化动作就会咽喉,从而也就变得
安全无误了。
采用单例设计模式的一个简单实现,以所谓“缓式求值”形式完成静态指针的初始化动作。
因此,我们能够保证Environment对象的数量不会超过一个。请注意,Environment类型
没有给予其构造函数 public 访问层级,所以Environment类型的用户只能用它公开出来的
instance()成员函数来取得这个静态指针。而且,我们不必在第一次访问Environment对象
之前创建它。
Environment::instance().op1();
更重要的是,这种受控的访问为使用了单件设计模式的类型适应未来的变化带来了灵活性,
并且消除了对现有代码的影响。以后当我们要切换到多线程的环境,或是要改成允许一种以上
的环境并存的设计,或是随便要求怎么变时,我们都可以通过更改使用了单件设计模式之
更难维护。阻碍重用是因为任何用了全局变量的代码就立刻与之耦合,这使得全局变量一改
它们也飞得跟着改,从而使任何重用都不可能了。它们使代码变得更难维护的原因是很难
分辨出哪些代码用了某个特定的全局变量,因为任何代码都有访问它们的权限。
全局变量增加了模块间的耦合,因为它们往往作为幼稚的模块间消息传递机制的设施存在。
就算它们能单词重任,从实践角度来说,要从大型软件的源代码去掉任何全局变量都几乎不
可能。这还假定它们能正常工作的情况。不过可不要忘了,全局变量是不设防的。随便哪个
维护你代码的C++新手,都能使对全局变量有强烈依赖的软件随时崩溃。
全局变量的辩护者们经常拿它的“方便”来说事。这真是自私自利之徒的无耻之争。要知道
软件的维护常常比它的初次开发要花费更多时间,而使用全局变量就意味着把烂摊子扔给
了维护工程师。假设我们有一个系统,它有一个全局可访问的"环境",并且(我们按需求保证)
确实只有“一个”。不幸的是,我们选择了使用全局变量(来表示它):
extern Environment * const theEnv;
在软件就要交付之前,我们会发现,可能同时存在的环境要增加到两个、三个,或是在系统
启动时指定的,或根本就是完全动态的某个数。这种在软件发布的最后时刻发生的变更实属
家常便饭。在备有无微不至的源代码控制过程的大项目里,这个变更会引发极费时间、涉及
所有源文件的更改,即使在最细小的和最直截了当的那些地方也不例外。整个过程预计要几天
到几星期不等。假如我们不用全局变量,只要五分钟我们就能搞定这一切:
Environment *theEnv();
仅仅是把对于值的访问加上了函数形式的包装,我们就获得了可贵的可扩充性。要是再
加上函数重载,或是给予函数形式参数以默认值,我们就根本不需要怎么改源代码了。
Environment *theEnv( EnvCode whichEnv = OFFICIAL);
另一个全局变量引起的问题并不能一望而知。此问题的来源是全局变量经常要求(延迟到)
运行期(才进行的)静态初始化,在C++语言中,如果静态变量用来初始化的值不能在编译
时就计算妥当,那么这个初始化的动作就会被拖到运行期。这是许多致命后果的始作俑者
extern Environment * const theEnv = new OfficalEnv;
如果改用函数或class来充当访问全局信息的东西,初始化动作就会咽喉,从而也就变得
安全无误了。
采用单例设计模式的一个简单实现,以所谓“缓式求值”形式完成静态指针的初始化动作。
因此,我们能够保证Environment对象的数量不会超过一个。请注意,Environment类型
没有给予其构造函数 public 访问层级,所以Environment类型的用户只能用它公开出来的
instance()成员函数来取得这个静态指针。而且,我们不必在第一次访问Environment对象
之前创建它。
Environment::instance().op1();
更重要的是,这种受控的访问为使用了单件设计模式的类型适应未来的变化带来了灵活性,
并且消除了对现有代码的影响。以后当我们要切换到多线程的环境,或是要改成允许一种以上
的环境并存的设计,或是随便要求怎么变时,我们都可以通过更改使用了单件设计模式之
类型的实现来搞定这一切,而这就像我们先前更改包装全局变量的那个函数一样随心所欲。
main.cpp
#include "environment.h"
int main() {
Environment::instance().op1();
return 0;
}
environment.h
#ifndef ENVIRONMENT_H
#define ENVIRONMENT_H
class Environment { // Singleton
public:
static Environment &instance();
virtual void op1() = 0;
// other operations...
protected:
Environment();
virtual ~Environment();
private:
static Environment *instance_;
// member data...
friend class Destroyer;
};
#endif
environment.cpp
#include <iostream>
#include "environment.h"
class OfficialEnv : public Environment {
public:
void op1()
{ std::cout << "OfficialEnv::op1" << std::endl; }
};
Environment::Environment()
{ std::cout << "Created" << std::endl; }
Environment::~Environment()
{ std:: cout << "Destroyed" << std:: endl; }
Environment *Environment::instance_ = 0;
Environment &Environment::instance() {
if( !instance_ )
instance_ = new OfficialEnv;
return *instance_;
}
// Simple mechanism for Singleton cleanup. Often works,
// but is far from foolproof.
class Destroyer {
public:
~Destroyer() { delete Environment::instance_; }
};
namespace {
Destroyer d;
}