v1.0——基本实现
1 | class Singleton { |
因为我们只需要一个 Singleton 实例,所以可以在 Singleton 类中声明一个静态的 Singleton 实例,在类外定义这个实例。然后将构造函数设为私有,这样就只能通过 Get 方法获取 Singleton 的实例,注意这里 Get 返回的是引用,毕竟我们只需要一个 Singleton 实例。
v2.0——解决"多例"问题
第一个版本看似已经不错了,但还是有缺陷的。如果我们的 main 函数是这样:
1 | int main() { |
我们在 main 函数中又定义了一个 Singleton 实例!并且将 Get 获得的静态 Singleton 实例拷贝赋值给了局部自动变量 instance,于是,我们的单例模式就宣告终结了。所以我们还需要将拷贝赋值以及拷贝构造等函数设为删除:
1 | class Singleton { |
这样的话,main 函数中第 19 行就必须使用引用了,如果不是引用,编译器就会报错。这样就实现了完整的 Singleton 模式。
v3.0——优化使用体验
假设我们有如下的类:
1 | class Random { |
这是一个 Random 类,调用 Float 方法可以返回一个随机数(这里假设 m_random_generator 是一个随机数生成器)。于是在 main 函数中,我们需要像 Random::Get().Float()
这样先获得实例,再去调用 Float 方法来获取随机数。Singleton 之所以是 Singleton,是因为它是一个类,可以拥有并使用自己的成员(成员函数使用成员变量 m_random_generator)。不然的话,我们可以直接将 Random 里面的东西都声明为静态的就好了,根本不需要 Singleton(比如将 Random 变成一个 namespace,然后在这个 namespace 中去声明函数和变量)。
然而为了支持类的特性,我们的 Singleton 不得不像上面代码那样非常麻烦的去调用 Get 再调用 Float。我们希望的是只需要像这样Random::Float()
使用即可。那么如何既可以像调用静态方法那样使用 Float 成员函数,又可以让成员函数使用成员变量呢(静态成员函数是不可以使用非静态成员变量的)?
Youtube 的大神给出的方法如下:
1 | class Random { |
是不是很聪明?只是又抽象了一层而已,我们将原来的 Float 成员函数设为私有,并更名为 IFloat(可以理解为 Internal Float 或者 Interface Float 或者 Implement Float),然后将 Float 方法以静态成员函数的形式暴露给调用者,之前获取实例的 Get 步骤本应该由 Float 的调用者去负责,现在我们将这一步交给了 Float 自己去做,并将 Float 变为了一个静态成员函数。
于是我们现在使用 Random 中的方法,就可以全部通过 Random::Method()
来操作了,而不需要自己去将 Random 进行实例化或者是需要先获取 Random 实例了。并且我们还拥有了所有类的特性(在方法中使用成员变量等)。
v4.0——线程安全优化
可以看到为了实现单例模式,我们在类中声明了一个静态的 Random 实例,然后在另外一个地方去定义了这个实例。这种写法会存在一些问题,比如在哪里定义这个实例比较合适?什么时候定义它比较合适?有些时候(比如多线程环境下)这些问题非常的关键。于是就有了以下写法:
1 | class Random { |
为了让代码更简洁、更安全,我们将之前的静态 Random 实例的声明以及定义放在了 Get 方法中,让其成为一个局部静态变量,这样的话,我们在第一次调用 Get 方法的时候,它就会实例化,并且之后调用 Get 方法,使用的都是同一个实例。
这样的写法除了让代码更简洁,还有可以让代码更安全。我们不用担心在使用 Random 的时候,Random 有没有被实例化,因为第一次调用 Get 方法时,我们就顺带进行了实例化操作。并且在 C++ 11 中,局部的静态变量是线程安全的(除非在编译时使用了 --fno-threadsafe-statics 参数),而单纯的静态成员变量是非线程安全的,非线程安全引起的 bug 有时可是很难发现的。