首页 设计模式之单例模式
文章
取消

设计模式之单例模式

之前做项目的时候,涉及到单例模式的有关内容,但之所以拿出来讲,不仅仅是因为设计模式,还涉及到static成员变量的有关知识点;

单例模式的概念

单例模式是一种设计模式,用于确保一个类只有一个实例,并提供一个全局访问点来访问该实例。

这在某些情况下很有用,比如当只需要一个对象来协调某些共享资源、控制特定操作的时候,或者为了避免创建大量相同的对象而造成资源浪费。

单例模式常用的一种场景,比如我设计了一个服务器,需要给服务器加上日志打印模块,显然在服务器运行过程中,我们只需要保证日志处理的实例只有一个,从而集中管理和控制日志输出,提高代码的可维护性和可读性。

单例模式的特点

一般而言,单例(模式)类有这些特点,它会包含:

  • 私有构造函数
    • 单例类之所以将构造函数设定为私有的,目的就是为了防止外部代码直接创建实例,失去单例模式的作用;
  • 静态实例变量
    • 单例类通常有一个静态变量,该变量主要是为了保存单例实例;
  • 静态成员函数
    • 该成员函数的目的主要是为了获取单例实例;

单例模式常用的实现方式有两种:

  • 饿汉式单例:在类加载的时候就创建单例实例,保证全局唯一性。优点是线程安全,但可能在实际使用时造成资源浪费,因为即使没有使用也会创建实例。
  • 懒汉式单例:在第一次使用时才创建单例实例,避免了资源浪费。但需要考虑线程安全问题,可以通过同步方法、双重检查锁等方式来实现线程安全。

单例模式在多线程环境下的使用确实是需要尤为注意的一点,因为某种程度上,单例模式类也是一种共享资源;

我们看一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Log {
public:
    // ....
    
    static Log* Instance(); // 该函数用来获取静态实例
    {
        static Log inst;    // 全局的日志实例,保证了在整个应用程序中只有这一个实例
        return &inst;       // 由于是static的,作用域结束也不会被销毁
    }

    // ....
private:
    // .....

    Log();  // 构造函数设计为private的

    virtual ~Log();

    // .....
};

显然,上面这段代码属于懒汉式单例;

此外,对单例类的使用还需要注意线程安全,但是一般而言我们作为服务器日志打印系统,服务器程序的实例正常情况下只有一个,因此只要是在服务端下调用日志,线程安全是有保证的;

说到这里,这边再添加一些额外的思考;

补充思考

这部分之所以配合单例模式理解,原因主要在于一个众所周知的知识点:类的静态成员函数不得访问类的非静态成员(函数);

  • 因为静态成员没有隐含的this指针,找不到类的(非静态)成员

那么,请问构造函数算类的成员函数吗?

如果算,为什么上述代码中可以在静态成员中去构造Log对象呢?

这是一个思考了许久的问题,目前的理解方式是这样的:静态成员确实因为没有隐含的this指针,无法访问类的非静态成员,但是访问类的构造函数,需要this指针吗?显然,在构造函数执行之前,甚至不存在所谓的this指针,类似我们看这么一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
using namespace std;

class A {
public:
    A() {}
    void func() { cout << "This is func" << endl; }
    ~A() {}    
}

int main() {
    A a;    // 执行这段需要this指针吗?不需要

    a.func();   // 通过隐含的this指针访问func()

    func();     // 访问错误,找不到该函数
}

因此类比到类的静态成员函数,静态成员函数在自身的作用域内,是可以通过构造函数去构造对象的,就像main函数,可以在自己的作用域内构造对象一个道理;

不知道目前的理解够不够准确,随时补充!

本文由作者按照 CC BY 4.0 进行授权

局域网内访问WSL

协程的介绍