c++ Singleton 单例

1650次浏览

单例模式也称为单件模式、单子模式,可能是使用最广泛的设计模式。其意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。有很多地方需要这样的功能模块,如系统的日志输出,GUI应用必须是单鼠标,MODEM的联接需要一条且只需要一条电话线,操作系统只能有一个窗口管理器,一台PC连一个键盘。

单例模式有许多种实现方法,在C++中,甚至可以直接用一个全局变量做到这一点,但这样的代码显的很不优雅。 使用全局对象能够保证方便地访问实例,但是不能保证只声明一个对象——也就是说除了一个全局实例外,仍然能创建相同类的本地实例。

《设计模式》一书中给出了一种很不错的实现,定义一个单例类,使用类的私有静态指针变量指向类的唯一实例,并用一个公有的静态方法获取该实例。
单例模式通过类本身来管理其唯一实例,这种特性提供了解决问题的方法。唯一的实例是类的一个普通对象,但设计这个类时,让它只能创建一个实例并提供对此实例的全局访问。唯一实例类Singleton在静态成员函数中隐藏创建实例的操作。习惯上把这个成员函数叫做Instance(),它的返回值是唯一实例的指针。
定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class CSingleton
{
private:
	CSingleton()   //构造函数是私有的
	{
	}
	CSingleton(const CSingleton &);  
	CSingleton & operator = (const CSingleton &); 
 
	static CSingleton *m_pInstance;
public:
	static CSingleton * getInstance()
	{
		if(m_pInstance == NULL)  //判断是否第一次调用
			m_pInstance = new CSingleton();
		return m_pInstance;
	}
};

用户访问唯一实例的方法只有getInstance()成员函数。如果不通过这个函数,任何创建实例的尝试都将失败,因为类的构造函数是私有的。getInstance()使用懒惰初始化,也就是说它的返回值是当这个函数首次被访问时被创建的。这是一种防弹设计——所有getInstance()之后的调用都返回相同实例的指针:
CSingleton* p1 = CSingleton :: getInstance();
CSingleton* p2 = p1-> getInstance();
CSingleton & ref = * CSingleton :: getInstance();

关于Singleton(const Singleton);和 Singleton & operate = (const Singleton&);函数,需要声明成私有的,并且只声明不实现。这样,如果如果通过 CSingleton copy = *CSingleton::getInstance()等方式来使用单例时,不管是在友元类中还是其他的,编译器都是报错。如果在c++11中,可以Singleton(const Singleton) = delete;来明确函数是删除的。

单例类CSingleton有以下特征:
它有一个指向唯一实例的静态指针m_pInstance,并且是私有的;
它有一个公有的函数,可以获取这个唯一的实例,并且在需要的时候创建该实例;
它的构造函数是私有的,这样就不能从别处创建该类的实例。

大多数时候,这样的实现都不会出现问题。有经验的读者可能会问,m_pInstance指向的空间什么时候释放呢?更严重的问题是,该实例的析构函数什么时候执行?

如果在类的析构行为中有必须的操作,比如关闭文件,释放外部资源,那么上面的代码无法实现这个要求。我们需要一种方法,正常的删除该实例。
可以在程序结束时调用getInstance(),并对返回的指针掉用delete操作。这样做可以实现功能,但不仅很丑陋,而且容易出错。因为这样的附加代码很容易被忘记,而且也很难保证在delete之后,没有代码再调用getInstance函数。

一个妥善的方法是让这个类自己知道在合适的时候把自己删除,或者说把删除自己的操作挂在操作系统中的某个合适的点上,使其在恰当的时候被自动执行。
我们知道,程序在结束的时候,系统会自动析构所有的全局变量。事实上,系统也会析构所有的类的静态成员变量,就像这些静态成员也是全局变量一样。利用这个特征,我们可以在单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例。如下面的代码中的CGarbo类(Garbo意为垃圾工人):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class CSingleton
{
private:
	CSingleton()
	{
	}
	CSingleton(const CSingleton &) = delete;  
	CSingleton & operator = (const CSingleton &) = delete; 
 
	static CSingleton *m_pInstance;
	class CGarbo   //它的唯一工作就是在析构函数中删除CSingleton的实例
	{
	public:
		~CGarbo()
		{
			if(CSingleton::m_pInstance)
				delete CSingleton::m_pInstance;
		}
	};
	static CGarbo Garbo;  //定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数
public:
	static CSingleton * getInstance()
	{
		if(m_pInstance == NULL)  //判断是否第一次调用
			m_pInstance = new CSingleton();
		return m_pInstance;
	}
};

类CGarbo被定义为CSingleton的私有内嵌类,以防该类被在其他地方滥用。
程序运行结束时,系统会调用CSingleton的静态成员Garbo的析构函数,该析构函数会删除单例的唯一实例。
使用这种方法释放单例对象有以下特征:
在单例类内部定义专有的嵌套类;
在单例类内定义私有的专门用于释放的静态成员;
利用程序在结束时析构全局变量的特性,选择最终的释放时机;
使用单例的代码不需要任何操作,不必关心对象的释放。

进一步的讨论
但是添加一个类的静态对象,总是让人不太满意,所以有人用如下方法来重新实现单例和解决它相应的问题,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
class CSingleton
{
private:
	CSingleton()   //构造函数是私有的
	{
	}
	CSingleton(const CSingleton &) = delete;  
	CSingleton & operator = (const CSingleton &) = delete;  
public:
	static CSingleton * getInstance()
	{
		static CSingleton instance;   //局部静态变量
		return &instance;
	}
};

一般来说一个程序里会有大量的单例,如果每个都这么写肯定是很累的,那么就考虑实现一个单例模板。一般来说呢,会有好几个版本的模板实现。这里就不一一说明了,只提供我自己觉得算是比较好的一种方式,同时也包括了原子操作及非原子操作。该代码使用了c++11特性。
如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
 
class $atomic {};        // 原子,线程安全
class $nonatomic {};     // 非原子,非线程安全
 
template <class T, class Atomicable = $nonatomic> 
class Singleton
{
 
protected:
    Singleton() {}
    virtual ~Singleton() {}
 
public:
    static inline T* getInstance();
 
private:
    static T* __getInstance(const $nonatomic&);
    static T* __getInstance(const $atomic&);
};
 
template inline T* Singleton<T, Atomicable>::getInstance()
{
    static_assert(std::is_same<Atomicable, $nonatomic>::value
                  || std::is_same<Atomicable, $atomic>::value, "Atomicable must be nonatomic or atomic");
 
    // 参考STL,用类型来规避 if...else...
    static T* s = __getInstance(Atomicable());
    return s;
}
 
template T* Singleton<T, Atomicable>;::__getInstance(const $nonatomic&)
{
    static std::auto_ptr auto_s;
    if (auto_s.get() == nullptr) {
        auto_s.reset(new T);
    }
    return auto_s.get();
}
 
template T* Singleton<T, Atomicable>::__getInstance(const $atomic&)
{
    static std::auto_ptr auto_s;
 
    if (auto_s.get() == nullptr) {
        static std::mutex mutex;
        mutex.lock();
        if (auto_s.get() == nullptr) {
            auto_s.reset(new T);
        }
        mutex.unlock();
    }
 
    return auto_s.get();
}
 
#define SINGLETON_DECLARE(T, ...)    \
public: \
static T* getInstance() { return Singleton<T, ##__VA_ARGS__>::getInstance(); } \
private:    \
    T();    \
    T(const T&) = delete;    \
    T&amp; operator=(const T&) = delete; \
    friend class Singleton<T, ##__VA_ARGS__>;
 
 
/******** Singleton Simple *********/
/*
 // Simple.h
 // =================================
 class Simple
 {
    SINGLETON_DECLARE(Simple, [ $nonatomic | $atomic ])
 public:
 
 };
 
 // Simple.cpp
 // =================================
 Simple::Simple()
 {
 
 }
 
 **************************************/

之所以在__getInstance(const $atomic&)函数里面对auto_s.get() == nullptr 是否为空做了两次判断,因为该方法调用一次就产生了对象,pInstance == nullptr 大部分情况下都为false,如果按照原来的方法,每次获取实例都需要加锁,效率太低。而改进的方法只需要在第一次调用的时候加锁,可大大提高效率。在第二次判断是因为,可能当mutex.lock()成功后,auto_s.get()已经不为空了。虽然概率很低,但在多线程的情况下还是要必须规避的。

实现的略有点复杂,不过用起来只用一行代码就要可以了。还有一个额外的功能是,不管什么类,通过Singleton::getInstance()都将得到一个该类的单例。也就是说你甚至可以不在类的头里声明SINGLETON_DECLARE,也可以使用单例。

释迦牟尼:最经典和灵性的4句话

497次浏览

1.无论你遇见谁,他都是在你生命中该出现的人。
这意味,没有人是因为偶然进入我们的生命。每个在我们周围,
和我们有互动的人,都代表一些事。也许要教会我们什么,也许
要协助我们改善眼前的一个情况。
  
2.无论发生什么事,那都是唯一会发生的事。
我们所经历的事,不可能以其它的方式发生,即便是最不重要的
细节也不会。 无论发生什么事,那都是唯一会发生的,而且一定
要那样发生,才能让我们学到经验以便继续前进。生命中,我们
经验的每一种情境都是绝对完美的,即便它不符我们的理解与自尊。
  
3.不管事情开始于哪个时刻,都是对的时刻。
每一件事都正好是在对的时刻开始的,不早也不晚。当我们准备
好,准备经历生命中的新奇时刻,它就在哪里,随时准备开始。
  
4.已经结束的,就已经结束了。
这是如此简单。当生命中有些事情结束,它会帮助我们进化。这
是为什么,要完整享受已然发生的事,最好是放下并持续前进。
你坐在这里,读着这些文字,我相信绝非巧合。

cocos2dx3.10 TiledMap超大显示

890次浏览

cocos2dx支持TiledMap的加载和显示,挺方便的。不过方便之余也有诸多的限制,如一个图集不能放在两个层上,还有就是当格子数超过一定数量时就不显示了。

图集问题就不说了,做的时候小心一点就可以了。下面来说说超大地格的显示问题。基本思路是当地图移动的时候,重新计算层的VBO。具体代码请下载查看,这里就不贴了。

以下是应用示例:

1
2
3
4
5
6
7
8
9
10
auto tiledMap = tmx::EasyTMXTiledMap::create("map.tmx");
tiledMap->setAnchorPoint(Vec2(0.5, 0.5));
tiledMap->setPosition(tiledMap->getContentSize() * 0.5);
auto scroll = cocos2d::ui::ScrollView::create();
scroll->setContentSize(getContentSize());
scroll->setInnerContainerSize(tiledMap->getContentSize());
scroll->setDirection(cocos2d::ui::ScrollView::Direction::BOTH);
scroll->addChild(tiledMap);
scroll->jumpToPercentBothDirection(Vec2(50, 50));
addChild(scroll);

代码下载:
EasyTXMTiledMap.zip