《C++设计模式》单例模式

文章目录

1、简介

单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取该实例。这个模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。

以下是单例模式的一些关键点

(1)私有构造方法 :为了防止外部代码通过new关键字创建类的多个实例,单例模式的构造方法是私有的。

(2)静态变量 :类的唯一实例通常存储在一个静态变量中,这样它可以被类的所有实例共享和访问。

(3)公共静态方法 :为了提供对唯一实例的全局访问,单例模式通常包含一个公共的静态方法,该方法在需要时返回类的唯一实例。如果实例尚不存在,该方法会创建它;如果实例已经存在,则直接返回该实例。

(4)线程安全:在多线程环境中,需要确保单例模式的实现是线程安全的,以防止多个线程同时创建类的多个实例。这通常通过同步机制来实现。

2、单例模式的种类

2.1 饿汉式单例模式:

(1)在类加载时就创建实例。

(2)线程安全,因为实例在类加载时就已经存在,不存在多线程竞争问题。

(3)可能导致内存浪费,因为即使从未使用过该实例,它仍然会被创建。

(4)实现简单,通常通过静态变量和静态代码块来完成。

2.2 懒汉式单例模式:

(1)在第一次使用时才创建实例。

(2)非线程安全,因为多个线程可能同时进入创建实例的代码块,导致创建多个实例(除非添加额外的同步机制)。

(3)节省内存资源,因为实例只在需要时才创建。

(4)实现相对复杂,需要处理线程安全问题,通常通过同步方法或双重检查锁定来实现。

3、单例模式的具体介绍

3.1、饿汉式

3.1.1、代码示例

cpp 复制代码
#include <iostream>

class Singleton {
public:
    // 禁用拷贝构造函数和赋值运算符,确保单例的唯一性
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    // 提供一个公共的静态方法来获取单例对象
    static Singleton& getInstance() {
        static Singleton instance; // 静态局部变量,只在第一次调用时构造
        return instance;
    }

    // 一个示例方法,用于展示单例对象的使用
    void doSomething() {
        std::cout << "Doing something in Singleton instance!" << std::endl;
    }

private:
    // 私有构造函数,防止外部直接创建实例
    Singleton() {
        std::cout << "Singleton instance created!" << std::endl;
    }

    // 禁用析构函数,防止外部调用delete(实际上在程序结束时会自动调用)
    // 注意:对于饿汉式单例,析构函数通常不需要特别处理,因为实例会在程序结束时自动销毁
    // 但为了完整性,这里仍然声明为私有,并添加注释
    ~Singleton() = default;
};

int main() {
    // 获取单例对象并调用其方法
    Singleton& singleton = Singleton::getInstance();
    singleton.doSomething();

    // 尝试获取同一个单例对象(实际上仍然是同一个实例)
    Singleton& anotherSingleton = Singleton::getInstance();
    anotherSingleton.doSomething();

    // 由于拷贝构造函数和赋值运算符被禁用,以下代码会导致编译错误
    // Singleton copySingleton = Singleton::getInstance(); // 错误:拷贝构造函数被删除
    // Singleton assignSingleton;
    // assignSingleton = Singleton::getInstance(); // 错误:赋值运算符被删除

    return 0;
}

运行结果:

cpp 复制代码
Singleton instance created!
Doing something in Singleton instance!
Doing something in Singleton instance!

3.1.2、组成部分

(1)私有静态成员变量:

这是一个静态的类成员变量,用于存储单例实例。由于它是私有的,所以外部类不能直接访问或修改它。

(2)私有构造函数:

类的构造函数被声明为私有,以防止外部类通过new关键字创建新的实例。这是实现单例模式的关键之一。

(3)公共静态方法:

提供一个公共的静态方法(通常是getInstance),用于返回单例实例。这个方法检查静态成员变量是否已经持有实例,如果还没有,则创建一个新的实例并返回;如果已经存在,则直接返回该实例。

(4)静态初始化块(可选):

在某些情况下,单例实例的初始化可能需要执行一些复杂的逻辑,这时可以使用静态初始化块来完成。然而,在饿汉模式中,由于实例在类加载时就已经被创建,所以通常不需要静态初始化块。

3.1.3、优缺点

(1) 优点:线程安全,实现简单。

(2) 缺点:可能导致内存浪费,因为即使从未使用过该实例,它仍然会被创建。

3.1.4、应用场景

(1)线程安全需求高:由于饿汉模式在类加载时就创建了实例,所以它是线程安全的,无需额外的同步机制。这在多线程环境中尤其有用,可以避免竞争条件和潜在的线程安全问题。

(2)实例创建开销小:如果实例的创建开销很小,或者实例的创建和初始化过程不会消耗太多资源,那么饿汉模式是一个很好的选择。因为即使实例在类加载时被创建,也不会对系统性能产生显著影响。

(3)实例需要提前准备:在某些情况下,实例需要在类加载时就准备好,以便在后续的代码执行过程中随时使用。例如,某些配置信息或资源需要在应用程序启动时就被加载和初始化。

3.2、懒汉式

3.2.1、代码示例

cpp 复制代码
#include <iostream>
#include <mutex>
#include <memory>

class Singleton {
public:
    // 禁用拷贝构造函数和赋值运算符
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    // 获取单例实例的静态方法
    static Singleton* getInstance() {
        // 使用双重检查锁定(Double-Checked Locking)来确保线程安全和性能
        if (instance_ == nullptr) {
            std::lock_guard<std::mutex> lock(mutex_);
            if (instance_ == nullptr) {
                instance_ = new Singleton();
            }
        }
        return instance_;
    }

    // 示例方法
    void doSomething() {
        std::cout << "Singleton instance is doing something!" << std::endl;
    }

    // 析构函数,用于清理资源
    ~Singleton() {
        std::cout << "Singleton instance is being destroyed!" << std::endl;
    }

private:
    // 私有构造函数,防止外部创建实例
    Singleton() {
        std::cout << "Singleton instance is being created!" << std::endl;
    }

    // 静态成员变量,保存单例实例
    static Singleton* instance_;
    // 静态互斥锁,用于线程同步
    static std::mutex mutex_;
};

// 初始化静态成员变量
Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;

int main() {
    // 获取单例实例并调用示例方法
    Singleton* singleton1 = Singleton::getInstance();
    singleton1->doSomething();

    // 再次获取单例实例(应该是同一个实例)
    Singleton* singleton2 = Singleton::getInstance();
    singleton2->doSomething();

    // 注意:main函数结束时,单例实例的析构函数会被调用
    return 0;
}

3.2.2、组成部分

(1)私有的构造函数

懒汉式单例模式通过将构造函数私有化,防止外部通过new关键字直接创建多个实例。这是实现单例模式的关键一步,因为如果没有私有化构造函数,外部代码就可以随意创建类的实例,从而破坏单例模式的规则。

(2) 私有的静态实例变量

懒汉式单例模式使用一个私有的静态实例变量来存储唯一的实例。这个变量在类加载时不会被初始化,而是在第一次调用获取实例的方法时才会被创建。由于该变量是静态的,因此它只会在类的整个生命周期中存在一个实例。

(3) 公共的静态方法

懒汉式单例模式通过提供一个公共的静态方法来获取唯一的实例。这个方法首先会检查私有的静态实例变量是否为null,如果是,则创建一个新的实例并将其赋值给该变量。如果实例已经存在,则直接返回该实例。

(4)线程安全机制(可选)

在多线程环境下,懒汉式单例模式可能会出现多个线程同时创建实例的情况。为了解决这个问题,可以在获取实例的方法中添加同步机制,如使用synchronized关键字或java.util.concurrent包中的锁机制来确保只有一个线程能够创建实例。

3.2.3、优缺点

(1) 优点:节省内存资源,因为实例只在需要时才创建。

(2) 缺点:非线程安全,因为多个线程可能同时进入创建实例的代码块,导致创建多个实例。需要额外的同步机制来保证线程安全。

3.2.4、应用场景

(1)延迟加载:如果实例的创建开销较大,或者实例的创建和初始化过程会消耗较多资源,那么懒汉模式可以通过延迟加载来节省资源。只有在实际需要使用时,才会创建和初始化实例。

(2)节省内存:在某些情况下,如果实例在类加载时并不需要使用,那么懒汉模式可以避免不必要的内存占用。只有在需要时,才会分配内存和创建实例。

(3)实例创建依赖条件:如果实例的创建依赖于某些条件或参数,并且这些条件或参数在类加载时并不确定,那么懒汉模式可以根据实际情况来创建实例。这提供了更大的灵活性和适应性。

4、面试常问问题

对于C++的单例模式,面试高频问题主要集中在单例模式的基本概念、应用场景、实现方式及其相关细节上。以下是一些可能的面试问题及简要解答:

一、单例模式的基本概念

(1)什么是C++中的单例模式?

单例模式是一种设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问该实例。在C++中,这通常通过私有化构造函数、拷贝构造函数和赋值运算符,并提供一个静态的公共方法来获取实例来实现。

(2)单例模式的主要目的是什么?

单例模式的主要目的是确保整个进程中,只有一个类的实例,并且提供一个统一的访问接口。这通常用于需要全局访问并共享资源的场景,如日志记录器、配置管理器和数据库连接器等。

二、单例模式的应用场景

(1)请列举一些C++中单例模式的应用场景?

日志记录器:确保整个应用程序中只有一个日志记录器实例,以便集中管理日志输出。

配置管理器:用于读取和管理应用程序的配置信息,确保配置信息的一致性和全局可访问性。

数据库连接器:在需要连接数据库时,确保只有一个数据库连接器实例,以减少资源消耗和提高性能。

三、单例模式的实现方式

(1)C++中实现单例模式有哪些常见的方法?

饿汉式:在类加载时就初始化实例,线程安全但可能会浪费空间。

懒汉式:在第一次调用时才初始化实例,实现了懒加载,但需要注意线程安全问题。可以通过加锁或使用双重检查锁来确保线程安全。

静态内部类实现:利用C++11中的局部静态变量特性,在第一次调用时初始化实例,线程安全且效率高。

(2)什么是双重检查锁(DCL)?它在C++单例模式中的作用是什么?

双重检查锁是一种优化技术,用于在懒汉式单例模式中确保线程安全的同时提高效率。它首先检查实例是否已经存在,如果不存在则加锁,然后再次检查实例是否存在(以避免在加锁期间其他线程已经创建了实例),最后创建实例。这样可以减少不必要的加锁操作,提高性能。

(3)为什么C++11中的局部静态变量可以用于实现线程安全的单例模式?

C++11标准保证了局部静态变量的初始化是线程安全的。因此,在静态方法中利用局部静态变量来初始化单例实例,可以确保在多线程环境中只有一个线程能够创建实例,而其他线程将直接获取到已经创建的实例。

四、单例模式的进阶问题

(1)如何防止C++中的单例模式被反射攻击?

在C++中,由于不存在像Java那样的反射机制,因此通常不需要特别考虑防止反射攻击的问题。但是,如果使用了某些支持反射的库或框架,则需要通过适当的措施来防止反射攻击,如私有化构造函数和析构函数等。

(2)如何确保C++中的单例模式在序列化和反序列化过程中保持唯一性?

在C++中,如果单例类需要被序列化,则需要实现自定义的序列化逻辑。在反序列化过程中,可以检查是否已经存在实例,如果存在则直接返回该实例而不是创建新实例。这通常需要在序列化时保存一些额外的信息来标识实例的唯一性。

(3)C++中的单例模式是否可以被删除或重置?

在大多数情况下,单例模式不需要被删除或重置。因为单例实例通常在整个应用程序的生命周期内都存在,并且被多个模块或组件共享。然而,在某些特殊情况下(如单元测试或应用程序重置等),可能需要提供一种机制来删除或重置单例实例。这可以通过在单例类中添加一个专用的销毁函数来实现,但需要注意线程安全和资源释放等问题。

5、总结

单例模式是一种简单而有效的设计模式,但在使用时需要注意其优缺点和注意事项,以确保其正确性和高效性。

6、参考文章

本文章由文心一言搜索的来的。

相关推荐
吾当每日三饮五升1 小时前
C++单例模式跨DLL调用问题梳理
开发语言·c++·单例模式
猫武士水星2 小时前
C++ scanf
开发语言·c++
捕鲸叉3 小时前
QT自定义工具条渐变背景颜色一例
开发语言·前端·c++·qt
Rossy Yan3 小时前
【C++面向对象——群体类和群体数据的组织】实现含排序功能的数组类(头歌实践教学平台习题)【合集】
c++·排序算法·面向对象·封装·查找
我搞slam4 小时前
全覆盖路径规划算法之BCD源码实现(The Boustrophedon Cellular Decomposition)
c++·算法·图搜索算法
Rossy Yan4 小时前
【C++数据结构——查找】二分查找(头歌实践教学平台习题)【合集】
开发语言·数据结构·c++·算法·查找·头歌实践教学平台·合集
埃菲尔铁塔_CV算法5 小时前
BOOST 在计算机视觉方面的应用及具体代码分析(二)
c++·人工智能·算法·机器学习·计算机视觉
咖啡の猫5 小时前
原型模式详解与实践
设计模式·原型模式
臣妾写不来啊5 小时前
创建型模式5.单例模式
单例模式
青岚岁叶8 小时前
设计模式——泛型单例类
单例模式·设计模式·unity3d