TypechoJoeTheme

鱼一的博客 ◡̈

yuyi

知不可乎骤得,托遗响于悲风
网站页面
标签搜索
c++

单例模式

单例模式(Singleton Pattern)是一种常用的软件设计模式,其核心目的是确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。这种模式特别适用于那些系统中需要通过一个单一对象来协调操作的场景,例如配置管理器、线程池、缓存、日志对象等。

在单例模式的实现中,通常会有以下几个特点:

  1. 私有的构造函数:为了避免外部通过 new 或其他方式创建多个实例,单例类的构造函数是私有的。
  2. 持有自身的静态私有实例:单例类内部会自行创建一个静态私有实例,这保证了全局只有一个此类的实例。
  3. 公共的静态方法访问实例:单例类提供一个公共的静态方法(通常命名为 instance()getInstance()),用于全局访问唯一的实例。
  4. 线程安全(可选,根据需要):如果单例需要在多线程环境中使用,其实例化过程需要是线程安全的。
class singlePattern {
private:
    singlePattern() {};
    static singlePattern* p;
public:
    static singlePattern* instance();

    class CG {
    public:
        ~CG() {
            if (singlePattern::p != nullptr) {
                delete singlePattern::p;
                singlePattern::p = nullptr;
            }
        }
    };
};

singlePattern* singlePattern::p = new singlePattern();
singlePattern* singlePattern::instance() {
    return p;
}

singlePattern 类实现了单例模式的一种形式:

  • 私有构造函数:确保不能直接从类外部实例化。
  • 静态私有实例:通过 static singlePattern* p = new singlePattern(); 创建了一个静态私有实例。
  • 公共静态方法:通过 static singlePattern* instance() 方法提供了访问这个实例的全局访问点。
  • 垃圾回收机制:内部类 CG(可能指代清理者 Garbage Collector)在其析构函数中负责清理 singlePattern 的实例,这是一种确保在程序结束时清理资源的技术。

singlePattern 单例模式实现中,内部的类 CG是一个内嵌的嵌套类,主要用于管理单例对象的生命周期,特别是它的销毁。

这种设计模式的主要目的是确保单例对象在程序结束时能被正确地销毁。CG 类的作用是在其析构函数中销毁 singlePattern 的实例。这是一种在 C++ 中管理静态或全局资源的常用技术,称为 注册销毁者 (Register Destructor) 模式。它利用了局部静态对象在程序结束时自动调用其析构函数的特性。这种方法通常被用来处理资源释放、内存清理等任务,特别是在处理静态或全局对象时。

下面是对这种模式工作原理的简要说明:

  1. 静态实例singlePattern 类有一个静态私有成员 p,这是单例的唯一实例。
  2. 全局访问点instance() 方法提供全局访问单例实例的方式。
  3. 资源管理CG 类在全局或静态作用域中创建一个静态实例。因为 C++ 保证了静态对象的析构函数在程序结束(main函数结束或 dllmain 中的 DLL_PROCESS_DETACH)时调用,所以这个静态 CG 实例的析构函数将在程序结束时被调用。
  4. 析构函数CG 的析构函数负责检查 singlePattern 的静态实例 p 是否存在,如果存在,则删除它并将指针设置为 nullptr。这样确保了单例对象在程序生命周期结束时被正确清理,避免了内存泄漏。

通过这种方式,singlePattern 的单例实现不仅确保了全局唯一的实例访问,并且也管理了资源的正确释放。这是一个既简洁又有效的方法,特别是在需要确保资源正确释放的复杂系统中。

在单例模式的实现中,选择在内部类 CG 的析构函数中处理单例的销毁,而不是在单例类 singlePattern 自身的析构函数中处理,主要是基于以下考虑:

1. 单例类的生命周期控制

单例模式的核心是确保类的实例只有一个,并且全局可访问。由于单例对象通常是静态的,其生命周期与程序的整个运行时间相同。单例类的析构函数通常不会被显式调用,因为单例对象通常不会在程序运行中被销毁,而是在程序结束时由操作系统回收资源。

2. 避免重复销毁

如果单例的析构函数中包含了销毁自己的代码(即 delete this),这可能会导致程序运行出错。因为静态对象(例如单例实例)会在程序结束时由运行时环境自动销毁。如果在单例的析构函数中已经执行了销毁操作,当运行时环境再次尝试销毁这个已经被销毁的对象时,会引发未定义行为(通常是崩溃或内存错误)。

3. 自动和安全的资源管理

利用一个内部类(如 CG)来管理单例的销毁,可以提供更加自动化和安全的资源管理。CG 的静态实例会确保在正确的时机(程序结束时)自动调用其析构函数,这时再去安全地销毁单例实例,避免了潜在的重复销毁问题或是手动管理资源的错误。

4. 编程习惯和模式一致性

在设计模式实践中,通常推荐将资源管理和业务逻辑分离。单例模式主要关注于控制实例的创建和全局访问,而将资源的清理交给内部类,可以保持单例类的简洁和专注于其主要责任。这样做也使得代码更加清晰,更易于维护。

总结来说,通过使用一个专门的内部类来管理单例的生命周期和资源清理,可以更好地封装功能,减少错误,并使单例模式的实现更符合良好的设计原则和实践。这种方法既避免了单例自销毁带来的问题,也保证了程序在结束时能正确地清理资源。

这个实现虽然提供了基本的单例模式结构,但它存在潜在的问题,尤其是在多线程环境下。例如,静态实例的初始化不是线程安全的,可能在极端情况下导致多个实例被创建。此外,这种立即初始化(Eager Initialization)的方式会在程序启动时就创建实例,无论这个实例后续是否真的被用到,这可能会增加程序启动的负担,或在实例从未使用的情况下占用资源。对于多线程环境的线程安全和延迟初始化(Lazy Initialization),可能需要采用不同的策略,如双重锁定(Double-checked locking)等。

基于先前的讨论,下面将提供一个更健壯的单例模式的实现。这个实现将确保单例的唯一性,适应多线程环境,并且避免资源管理问题。这里使用C++11标准的线程安全保证来实现单例模式:

单例模式完整实现代码

#include <iostream>

class singlePattern {
private:
    // 私有构造函数确保不能在类外部实例化
    singlePattern() {
        std::cout << "singlePattern created." << std::endl;
    }

    // 禁止拷贝构造函数和赋值操作
    singlePattern(const singlePattern&) = delete;
    singlePattern& operator=(const singlePattern&) = delete;

public:
    // 提供全局访问点
    static singlePattern& instance() {
        static singlePattern instance;  // 局部静态变量,由编译器保证线程安全
        return instance;
    }

    // 示例方法,用于显示功能
    void show() {
        std::cout << "singlePattern instance function call." << std::endl;
    }

    // 析构函数
    ~singlePattern() {
        std::cout << "singlePattern destroyed." << std::endl;
    }
};

int main() {
    // 获取单例对象并调用其方法
    singlePattern& sp = singlePattern::instance();
    sp.show();

    // 再次获取单例对象并调用其方法,将看到不会再次创建实例
    singlePattern& sp2 = singlePattern::instance();
    sp2.show();

    return 0;
}

代码说明

  1. 私有构造函数:确保无法在类外部直接实例化 singlePattern 类。
  2. 局部静态实例:在 instance() 方法中定义了一个局部静态变量 instance。这种方式是C++11后推荐的单例模式实现方法,因为它无需显式使用锁就可以保证线程安全。
  3. 禁用拷贝构造和赋值操作:通过删除拷贝构造函数和赋值操作符,防止对象被复制或赋值,这是单例模式的常见实践。
  4. 显示方法show() 方法是为了演示单例对象功能的简单方法。
  5. 析构函数:用于在程序结束时自动清理资源,这里的输出可以观察到单例何时被销毁。
  6. 主函数main() 中展示了如何访问单例实例并调用其方法。

这种实现方式简单而高效,尤其适用于需要确保全局唯一实例的场景,且自动应对多线程环境中的同步问题。

在之前给出的C++单例模式的实现中,我确实使用了析构函数,但情况有些不同于传统的动态分配内存的管理方式。

在那个示例中,单例实例是作为一个局部静态变量实现的,这意味着该实例是自动管理的,其生命周期跟随着程序的整个运行期。编译器保证了当程序退出时,这个局部静态对象的析构函数会被自动调用。这不仅确保了资源的正确释放,而且还遵守了RAII(Resource Acquisition Is Initialization,资源获取即初始化)的原则,这是C++资源管理的一种推荐模式。

局部静态对象的内存管理和析构函数:

  1. 自动内存管理

    • 局部静态变量的生命周期是在其第一次使用时开始,直到程序结束。这意味着你不需要手动管理这个单例对象的内存;它由编译器自动处理。
    • 在程序结束时,C++运行时会自动调用所有局部静态对象的析构函数,这包括我们的单例对象。
  2. 析构函数

    • 我在代码中定义了析构函数,主要是为了在资源被释放时提供反馈(例如打印一条消息),而不是因为需要处理复杂的资源释放逻辑。
    • 由于单例对象的生命周期自动管理,这里的析构函数更多是为了确保当程序正常退出时,能有序地进行资源清理和状态通知。

这种单例实现避免了在多线程环境中使用显式锁定机制来保证线程安全,而是利用了C++语言特性来简化实现并提高效率。对于大多数用途来说,这种方式是安全且有效的,尤其是在单例对象不需要在程序运行期间动态创建和销毁的情况下。

赞(0)
版权属于:

鱼一的博客 ◡̈

本文链接:

https://yuyi.monster/archives/233/(转载时请注明本文出处及文章链接)

评论 (0)

More Info for me 📱

IP信息

人生倒计时

今日已经过去小时
这周已经过去
本月已经过去
今年已经过去个月