标签:分配 静态变量 重要 特性 create instance 必须 函数 代码
定义:确保一个类只有一个实例,并为其提供一个全局的访问入口。
那么什么情况下使用单例?最常见的情况就是一个类需要与一个维持自身状态的外部系统进行交互,比如说打印机。大多数情况下都是多人共用一个打印机,这意味着可能由多个人同时向这个打印机发送打印任务,这个时候管理打印机的类就必须熟悉打印机的当前状态并协调这些任务的执行。这个时候就不允许存在多个打印机的实例,因为实例无法知道其他的实例所做的操作,也就无法进行整体的管理。
我们先看看最常见的单例的实现方式:
class FileSystem { public: static FileSystem& instance_() { if(instance_ == nullptr) { instance_ = new FileSystem(); } return instance_; } private: FileSystem(){} static FileSystem* instance_; };
c++11保证一个局部静态变量初始化只进行一次,哪怕实在多线程的情况下也是如此,所以c++11中这样写更优雅。
class FileSystem { public: static FileSystem& instance() { static FileSystem& instance_ = new FileSystem(); return instance_; } private: FileSystem(){} };
从代码实现上看,单例模式由以下几个特性:
(1)它是个全局变量
根据前人的经验,全局变量时有害的,我们应该远离全局变量。为什么了?
(2)它是个画蛇添足的方案
从定义上看出,单例模式其实是解决了两个问题:第一保证一个实例,第二提供已访问入口。保证一个单例是很有用的,但谁说我们希望谁都能操作它?而第二个问题,便利的访问通常是我们使用单例的主要原因。但这同时也会引出新的问题,比如一个日志类,一开始大家都使用这个单例的日志类时很方便,但随着项目的深入,对于日志的需求也复杂了起来,比如要求分类写入多个日志文件,这个时候因为你是单例,所以为了支持多个实例,你就要修改每个你调用这个类的地方,结果便利的访问也就不那么便利了。
(3)延迟初始化剥离了你的控制
延迟初始化也就是在第一次调用的时候初始化,这样也就不能保证你初始化的时机。这通常在对性能要求非常高的游戏中时不被允许的,设想一个音频单例单例,初始化需要几百毫秒,而且伴随着内存的分配,如果你的游戏进行中突然调用这个单例,则会进行初始化操作,这件带来不可接受的游戏掉帧和卡顿。而且也不利于内存布局的控制。
在游戏中通常使用这样的方式来实现单例模式:
class FileSystem { public: static FileSystem& instance() { return instance_; } private: FileSystem(){} static FileSystem instance_; };
(1)首先看看你需不需类
在游戏中,我看见了太多的“manager”类了,它们的初衷时为了管理其它对象,虽然有时确实有用,但我更多的时看到它被滥用。比如下面的一个例子:
class Bullet { public: int getX() const {return x_;} int getY() const {return y_;} void setX(int x) {x_=x;} void setY(int y) {y_=y;} private: int x_; int y_; }; class BulletManager { public: Bullet* create(int x,int y) { Bullet* bullet = new Bullet(); bullet->setX(x); bullet->setY(y); return bullet; } bool isOnScreen(Bullet& bullet) { return bullet.getX() >=0 && bullet.getY >=0 && bullet.getX() <= SCREEN_WIDTH && bullet.getY() <= SCREEN_HEIGHT; } void move(Bullet& bullet) { bullet.setX(bullet.getX() + 5); } };
这个例子有点极端,但现实中很多manager类简化后就是这样的一个逻辑。我们通过一个单例来管理Bullet,感觉上好像合理,但仔细分析后,发现这个manager根本就没有存在的必要,设计出这样一个类的人应该对OOP不太熟悉。首先我们分析这三个方法:
所以,修改后,我们只需要一个Bullet类:
class Bullet { public: Bullet(int x,int y):x_(x),y_(y) { } bool isOnScreen() { return x_ >=0 && y_ >=0 && x_ <= SCREEN_WIDTH && y_ <= SCREEN_HEIGHT; } void move() { x_ += 5; } };
这样修改后,类的设计显得更合理,更自然。我们完全不需要一个额外的manager单例来帮助我们管理,所以,在我们设计单例时,首先就要分析我们是否真的需要这个单例。
(2)将类限制为单一实例
我们使用单例模式,很多时候只是要限制该类只有一个实例,但这并不意味着我们要提供一个全局访问,我们可能只是想在某一部分代码中访问这个实例,这个时候如果使用单例模式提供一个全局的访问接口,将会削弱整体的框架。我们可以有几种方式避免这种情况的出现。比如:
class FileSystem { public: FileSystem() { assert(!instantiated); instantiated = true; } ~FileSystem() { instantiated = false; } private: static bool instantiated; }; bool FileSystem::instantiated = false;
通过一个断言,保证FileSystem只有一个实例。
(3)为实例提供便捷的访问方式
使用单例模式的另一个需求就是便利的访问,它能让我们随时随地的获取这个唯一的实例。但这与我们通用的编程准则不符,我们通常是在保证功能的情况下尽量限制变量使用的一个范围,这样我们就只需要记住它的地方机会少很多(想想全局变量带来的问题)。那在不适用单例模式的时候,我们还有什么其它的途径访问一个对象了?通常我们会有这么几种方式:
所以,我们应该在什么时候使用单例了?老师说,单例并没有你想象的那样重要,如果你要确保类只被实例化一次,可以简单的使用一个静态类,如果还不满足要求,可以使用一个静态的标识符在运行时检查是否只有一个实例被创建。不过使用与否还是要视你自己需求来定,但一定要防止单例模式的滥用,这不会给你带来任何的好处。
标签:分配 静态变量 重要 特性 create instance 必须 函数 代码
原文地址:https://www.cnblogs.com/xin-lover/p/10454337.html