单例模式
定义:
Ensure a class has one instance, and provide a global point of access to it.
确保一个类只有一个实例,并且提供一个全局访问的途径。
一个系统中我们定义一个文件系统来管理所有的文件操作,单例模式的经典实现如下:
1.经典实现
class FileSystem
{
public:
static FileSystem& instance()
{
// Lazy initialize.
if (instance_ == NULL) instance_ = new FileSystem();
return *instance_;
}
private:
FileSystem() {}
static FileSystem* instance_;
};
但这种实现在并发环境下无法正常运作,所以我们可以进行如下修改:
2.线程安全实现
class FileSystem
{
public:
static FileSystem& instance()
{
static FileSystem *instance = new FileSystem();
return *instance;
}
private:
FileSystem() {}
};
c++11中确保局部静态变量只初始化一次,所以是线程安全的。
3.这种单例实现方式的好处:
- 仅用到时才会创建
- 运行时初始化,这样就可以获取相关运行时的信息
- 可以创建单例的子类来实现跨平台
class FileSystem
{
public:
static FileSystem& instance();
virtual ~FileSystem() {}
virtual char* readFile(char* path) = 0;
virtual void writeFile(char* path, char* contents) = 0;
protected:
FileSystem() {}
};
FileSystem& FileSystem::instance() //根据不同的平台创建不同的实例
{
#if PLATFORM == PLAYSTATION3
static FileSystem *instance = new PS3FileSystem();
#elif PLATFORM == WII
static FileSystem *instance = new WiiFileSystem();
#endif
return *instance;
}
class PS3FileSystem : public FileSystem
{
public:
virtual char* readFile(char* path)
{
// Use Sony file IO API...
}
virtual void writeFile(char* path, char* contents)
{
// Use sony file IO API...
}
};
class WiiFileSystem : public FileSystem
{
public:
virtual char* readFile(char* path)
{
// Use Nintendo file IO API...
}
virtual void writeFile(char* path, char* contents)
{
// Use Nintendo file IO API...
}
};
4.单例带来的问题
4.1它是全局变量
- 对于大规模程序而言,全局变量使得代码出错定位变得困难。
- 会使各个模块间的耦合度上升。
- 由于是全局变量,它会导致并发程序间的交互变得复杂难以确定。
4.2懒汉初始化的弊端
- 在游戏进行中时,第一次遇到资源加载初始化,可能会导致一些卡顿。
- 内存分配的掌控难度加大,因为你不知道何时会将内存分配。
饿汉式单例:
class FileSystem
{
public:
static FileSystem& instance() { return instance_; }
private:
FileSystem() {}
static FileSystem instance_;
};
将实例变为静态变量可以解决这些问题,但这种包装的意义又何在,因为毕竟它的访问效果和全局静态变量一致,但方式更加麻烦。
5.较好的方式
显然在一个游戏中,完全避免全局变量也不现实,毕竟每个游戏都有一个游戏类,掌控着游戏总体的运行情况,所以我们可以尽量减少全局型的类,将它们放在原有的全局类中:
class Game
{
public:
static Game& instance() { return instance_; }
// Functions to set log_, et. al. ...
Log& getLog() { return *log_; }
FileSystem& getFileSystem() { return *fileSystem_; }
AudioPlayer& getAudioPlayer() { return *audioPlayer_; }
private:
static Game instance_;
Log *log_;
FileSystem *fileSystem_;
AudioPlayer *audioPlayer_;
};
//使用
Game::instance().getAudioPlayer().play(VERY_LOUD_BANG);