单例模式

单例模式(Singleton Pattern)是一种设计模式,它确保一个类只有一个实例,并且提供全局访问点来访问该实例。单例模式常用于管理全局状态或资源,比如数据库连接、日志系统等。

单例模式的特点

  1. 唯一性:确保类在整个程序的生命周期中只有一个实例。
  2. 延迟实例化:通常会在第一次使用时才创建实例,避免不必要的资源消耗。
  3. 全局访问点:提供一个静态方法获取唯一实例,方便全局使用。

一、C++ 实现单例模式

下面是 C++ 实现单例模式的例子:

步骤 1: 定义私有的构造函数

确保类的构造函数是私有的,外部无法通过 new 来创建对象。

cpp 复制代码
class Singleton {
private:
    // 私有构造函数,防止外部实例化
    Singleton() {
        std::cout << "Singleton instance created!" << std::endl;
    }

public:
    // 删除复制构造函数,防止复制对象
    Singleton(const Singleton&) = delete;
    
    // 删除赋值操作符,防止对象被赋值
    Singleton& operator=(const Singleton&) = delete;
    
    // 静态方法,提供全局访问点获取唯一实例
    static Singleton& getInstance() {
        static Singleton instance;  // 静态局部变量,保证唯一
        return instance;
    }

    // 普通方法,做一些事情
    void doSomething() {
        std::cout << "Doing something!" << std::endl;
    }
};
步骤 2: 使用单例
cpp 复制代码
int main() {
    // 获取单例实例
    Singleton& s1 = Singleton::getInstance();
    s1.doSomething();

    // 再次获取单例实例,依然是同一个对象
    Singleton& s2 = Singleton::getInstance();
    s2.doSomething();

    return 0;
}
输出结果:
Singleton instance created!
Doing something!
Doing something!
解释
  1. 私有构造函数Singleton() 是私有的,因此外部无法直接创建 Singleton 对象,确保了类只有一个实例。
  2. 静态方法 getInstance() :这是获取唯一实例的全局访问点。在第一次调用时,静态局部变量 instance 会被初始化,之后的每次调用都会返回同一个实例。
  3. 防止复制:通过删除复制构造函数和赋值操作符,防止创建新的副本。
  4. 静态局部变量static Singleton instance; 保证了单例的唯一性,并且只在第一次调用 getInstance() 时进行实例化,之后不会再创建新的对象。
线程安全的实现

上述代码在单线程环境下工作良好,但在多线程环境中可能会有并发问题。为了解决这个问题,C++11 之后引入了静态局部变量初始化的线程安全特性。因此,static Singleton instance; 在 C++11 之后默认是线程安全的。

总结:单例模式通过确保类的构造函数私有化,并提供静态方法获取唯一实例,控制了实例的数量。在 C++ 中,使用静态局部变量和 delete 复制构造等技巧可以很好地实现这一模式。

二、每一步代码的解释:

1. 私有构造函数
cpp 复制代码
private:
Singleton() {
    std::cout << "Singleton instance created!" << std::endl;
}
  • 作用 :单例模式的核心之一。通过将构造函数设置为 private,确保外部代码不能直接创建 Singleton 对象,只能通过类的静态方法 getInstance() 来获取实例。
  • 原因 :如果构造函数是 public 的,外部代码可以随意创建多个实例,违背了单例模式只允许一个实例的原则。
2. 删除复制构造和赋值操作符
cpp 复制代码
// private or public
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
  • 作用 :这两行代码删除了默认的复制构造函数和赋值操作符,防止用户通过复制或赋值来创建多个 Singleton 对象。
  • 原因 :即使构造函数是私有的,用户仍然可能通过 Singleton s2 = s1;s2 = s1; 来复制或赋值对象。这种方式也会违反单例模式的唯一性要求。
private or public?

虽然这两行代码通常放在 private 部分,但将它们声明为 public 也是合理的,并且在某些情况下可能更具可读性。无论它们是放在 private 还是 public 区域,效果都是一样的:阻止用户通过复制构造或赋值操作创建新的 Singleton 实例。

public 下声明的好处

  1. 更清晰的错误提示 :当这些操作符被放在 public 部分时,用户试图使用复制构造函数或赋值操作符时,编译器会直接报错并提示这些操作被禁用。如果放在 private 中,编译器可能只会提示这些函数是不可访问的,而不会明确指出它们被删除了。

  2. 遵循现代 C++ 的风格 :在 C++11 及之后,删除函数通常被放在 public 区域,以表明这些操作是显式被禁止的,而不是因为访问权限限制而导致的不可访问。

3. 静态方法 getInstance()
cpp 复制代码
public:
static Singleton& getInstance() {
    static Singleton instance;
    return instance;
}
  • 作用
    • 这是实现单例模式的关键点。getInstance() 是一个静态方法(static 方法),是类级别的方法,不依赖于类的实例,意味着你不需要先创建对象再调用它。它可以直接通过类名访问。比如:
cpp 复制代码
  Singleton::getInstance();
  • static Singleton instance; 是静态局部变量。这个变量只在第一次调用 getInstance() 时初始化,之后每次调用都会返回同一个对象。
  • 访问静态成员或静态资源:静态方法可以直接访问静态变量(例如在单例模式中使用的静态局部变量)。这使得静态方法可以用于管理某些全局资源,如单例模式中的唯一实例。
  • 原因
    • 静态方法能够独立于实例存在,这是它的最大优势。在单例模式中,我们希望全局访问点是一个不依赖对象的类方法,而静态方法正好符合这一要求。
    • static Singleton instance; 是 C++ 实现单例模式的经典方法。它利用静态局部变量的生命周期来保证对象的唯一性。静态局部变量在函数作用域中定义,但只会被初始化一次,并在整个程序运行期间保持有效。
4. 普通方法 doSomething()
cpp 复制代码
void doSomething() {
    std::cout << "Doing something!" << std::endl;
}
  • 作用:这是一个普通的成员函数,用来展示单例对象可以调用的功能。
  • 原因 :一旦获取了单例实例,你就可以调用类的其他非静态方法来执行某些操作。在这个例子中,doSomething() 方法就是展示如何使用单例的一个例子。

为什么这么写?

  1. 唯一性 :通过将构造函数设为 private 和删除复制、赋值操作符,确保了外部代码无法随意创建多个实例。
  2. 全局访问点 :静态方法 getInstance() 允许用户在任何地方通过 Singleton::getInstance() 访问唯一实例。这符合单例模式的设计思想。
  3. 懒加载 :使用 static Singleton instance; 实现了懒加载,意味着只有在第一次调用 getInstance() 时,实例才会被创建。这提高了程序的性能,因为在不使用 Singleton 的时候,不会浪费内存。
  4. 线程安全 :在 C++11 及以后的标准中,静态局部变量的初始化是线程安全的,因此即使在多线程环境中,多个线程同时访问 getInstance(),也不会出现多个实例的问题。

通过这些设计,单例模式确保了类只有一个实例,并且为用户提供了一个全局的、方便的访问接口。

三、Instance 很大怎么办

在 C++ 中,static 局部变量(例如 static Singleton instance;)的存储位置取决于系统的内存布局。具体来说,instance 被分配在静态存储区(也叫全局/静态数据区),而不是栈或堆中。

static 局部变量的存储位置

  • 静态存储区static 变量无论是在函数内还是类中定义,其生命周期从程序开始到程序结束。这类变量在程序启动时就被分配内存,并在整个程序运行期间都存在。
  • 特点
    • 全局生存期 :即使它是在一个函数中定义的(如 getInstance() 函数内部),static 变量的生命周期从第一次初始化开始,直到程序结束。
    • 唯一初始化:静态局部变量只会在第一次调用时进行初始化,之后每次调用都会返回同一个对象,不会重复创建。

因此,static Singleton instance; 在程序运行时,它的内存会被分配在静态数据区,直到程序结束时才释放。

如果 instance 很大该怎么做?

如果 Singleton 对象非常大,占用了大量内存,放在静态存储区可能不是一个好的选择。这时候,可以将对象的实际内容放到堆上,使用指针或智能指针来管理它。这样可以在需要时创建对象,并且控制其生命周期和内存释放。

使用动态内存分配的方式

修改 getInstance() 函数,将对象动态分配到堆上,通过指针来管理它。这里使用 std::unique_ptr(C++11 引入的智能指针)来保证内存的自动释放。

cpp 复制代码
#include <memory>  // 引入 std::unique_ptr

class Singleton {
private:
    Singleton() {
        std::cout << "Singleton instance created!" << std::endl;
    }

    // 删除复制构造函数和赋值操作符
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

public:
    // 静态方法返回唯一的实例指针
    static Singleton& getInstance() {
        // 静态智能指针,指向动态分配的 Singleton 对象
        static std::unique_ptr<Singleton> instance(new Singleton());
        return *instance;  // 返回解引用的实例
    }

    // 示例方法
    void doSomething() {
        std::cout << "Doing something!" << std::endl;
    }
};
解释
  1. std::unique_ptr<Singleton> instance :我们将 Singleton 对象动态分配在堆上,并使用 std::unique_ptr 来管理它。unique_ptr 会自动负责对象的内存释放,无需手动调用 delete

  2. static std::unique_ptr<Singleton> instance(new Singleton()); :静态智能指针只会在第一次调用时分配内存,并且整个程序中只会有一个 Singleton 实例。智能指针会自动在程序结束时释放 Singleton 的内存,避免内存泄漏。

  3. 返回解引用的对象*instance 是对智能指针所管理对象的解引用,因此 getInstance() 返回的是 Singleton 实例的引用,而不是指针。

优点
  • 动态分配内存:避免在静态存储区中直接分配大对象。对象被动态分配在堆上,且内存使用可以更加灵活。
  • 内存安全:通过智能指针管理对象,避免了手动管理内存所带来的风险(如内存泄漏、悬空指针等)。
  • 延迟加载Singleton 对象仍然会在第一次使用时才创建,节省资源。
总结
  1. 原实现static Singleton instance; 将对象存储在静态存储区中,适合小型或中等大小的对象。
  2. 动态内存管理 :如果对象很大,可以使用 std::unique_ptr 等智能指针,将对象动态分配到堆上,合理管理内存。这种方式使得内存更灵活、安全。

如果担心 instance 占用太多静态存储区的内存,使用动态分配的方案是一种良好的选择。

四、C++11 以前的单例模式

待补

五、补充

待补

六、多单例项目下的单例模板类

待补

相关推荐
思忖小下4 小时前
梳理你的思路(从OOP到架构设计)_简介设计模式
设计模式·架构·eit
liyinuo20176 小时前
嵌入式(单片机方向)面试题总结
嵌入式硬件·设计模式·面试·设计规范
aaasssdddd968 小时前
C++的封装(十四):《设计模式》这本书
数据结构·c++·设计模式
T1an-18 小时前
设计模式之【观察者模式】
观察者模式·设计模式
思忖小下10 小时前
梳理你的思路(从OOP到架构设计)_设计模式Factory Method模式
设计模式·工厂方法模式·eit
霁月风11 小时前
设计模式——工厂方法模式
c++·设计模式·工厂方法模式
发飙的蜗牛'13 小时前
23种设计模式
android·java·设计模式
重生之我在字节当程序员14 小时前
如何实现单例模式?
单例模式
夕泠爱吃糖14 小时前
如何实现单例模式?
单例模式
m0_6075487614 小时前
什么是单例模式
开发语言·javascript·单例模式