『 C++ 』单例模式与IO流

文章目录


单例模式

单例模式是一种创建型设计模式;

确保一个类在应用程序的生命周期内仅有一个实例并提供一个全局访问点来访问该实例;

单例模式主要目的是为了控制某些类的实例化以避免产生多个实例,从而节省资源和避免数据不一致问题;

单例模式的核心要点为如下:

  • 唯一性

    单例模式确保一个类只有一个实例,意味着该类的所有对象共享相同的状态和行为;

  • 全局访问点

    单例模式提供了一个静态方法(getInstance()),使得客户端可通过这个方法访问唯一的实例而不需要创建对象;

  • 加载方式

    单例模式可通过需求来自定义需要的加载方式,常见的加载方式为饿汉加载懒汉加载;

    • 饿汉加载

      饿汉加载指的是单例模式在进程创建时就对单例的资源进行初始化,从而间接提高运行的速度;

      因为进程启动时单例就已经被初始化意味着不需要再花时间对该单例进行初始化操作;

      相同的由于初始化的时机是在进程启动时,所以饿汉加载方式的启动速度要较慢;

      饿汉加载是线程安全的,但饿汉模式加载处的实例若是没有被使用则是一种空间的浪费的行为;

      尽管饿汉加载是线程安全的,也只是代表在加载过程中是安全的,若是实例中存在可能出现资源竞争的临界资源时同样必须为该单例考虑同步互斥问题;

    • 懒汉加载

      懒汉加载是单例的一种加载模式,懒汉加载模式旨在需要时对实例进初始化加载,从而提高进程的加载速度;

      由于懒汉加载模式是在需要时对实例进行加载,这意味着不需要花费时间在进程启动时对实例进行资源的加载;

      与饿汉模式不同,懒汉加载不是线程安全的,懒汉加载模式涉及到当需要该实例时多个线程同时调用加载函数对实例进行初始化加载,故在设计懒汉加载时需要考虑多线程并发情况下线程的同步与互斥问题;

      与饿汉模式不同的是懒汉模式不存在"一定要加载,不一定使用"的问题所引发的资源浪费的可能;

单例模式的实现步骤一般为:

  • 私有化构造函数

    通过私有化构造函数防止类外代码随意对实例进行控制从而可能产生多个实例;

  • 静态私有成员变量

    在类中声名一个静态的私有成员变量,用于存储该类的唯一实例,这个成员变量可以是一个对象也可以是一个指针变量,取决于加载方式;

  • 静态公有方法

    在单例模式中会提供一个静态共有方法(通常命名为getInstance())来获取该类的唯一实例;


饿汉加载的单例模式实现

cpp 复制代码
// 单例类的定义
class SingletonInstance {
public:
    // 静态方法,用于获取唯一实例
    static SingletonInstance& getInstance() {
        return instance_; // 返回静态实例
    }

    // 打印示例方法
    void Print() {
        std::cout << "This is a Singleton model" << std::endl;
    }

private:
    // 私有构造函数,防止外部实例化
    SingletonInstance() {
        std::cout << "SingletonInstance()" << std::endl;
    }

    // 私有析构函数,防止外部删除实例
    ~SingletonInstance() {
        std::cout << "~SingletonInstance()" << std::endl;
    }

    // 删除拷贝构造函数,防止复制
    SingletonInstance(const SingletonInstance&) = delete;

    // 删除赋值操作符,防止赋值
    SingletonInstance& operator=(const SingletonInstance&) = delete;

    // 静态成员变量,存储唯一实例
    static SingletonInstance instance_;
};

// 静态成员变量初始化
SingletonInstance SingletonInstance::instance_;

int main() {
    sleep(3); // 延迟3秒
    // 获取单例实例并调用打印方法
    SingletonInstance::getInstance().Print();
    return 0;
}

在这个单例模式中定义了一个单例类,通过静态成员变量instance_存储了唯一实例,该类内私有成员将在类外进行定义;

getInstance()为一个静态方法,用于返回类的唯一实例,由于为一个静态方法,其不隐含this指针,但其有权访问该类中的所有成员;

私有化构造函数以确保类外部无法创建或销毁单例实例从而保持单例模式的完整性;

通过删除拷贝构造和赋值重载防止通过赋值或拷贝的方式创建新的实例从而进一步保证单例模式的唯一性;


懒汉加载的单例模式实现

cpp 复制代码
class SingletonInstanceLazy {
 public:
  // 获取单例实例的静态方法
  static SingletonInstanceLazy* getInstance() {
    if (!instance_) { // 第一次检查实例是否为空
      lock_.lock(); // 加锁,确保线程安全
      if (!instance_) { // 再次检查实例是否为空,双重检查锁定
        instance_ = new SingletonInstanceLazy(); // 创建单例实例
      }
      lock_.unlock(); // 解锁
    }
    return instance_; // 返回单例实例
  }

 protected:
  // 构造函数和析构函数被保护以防止外部创建和销毁实例
  SingletonInstanceLazy() { cout << "SingletonInstanceLazy()" << endl; }
  ~SingletonInstanceLazy() { cout << "~SingletonInstanceLazy()" << endl; }

  // 禁止拷贝构造和赋值操作以防止生成多个实例
  SingletonInstanceLazy(const SingletonInstanceLazy&) = delete;
  SingletonInstanceLazy& operator=(const SingletonInstanceLazy&) = delete;

  // 嵌套类,用于程序退出时释放单例实例
  class Gc {
   public:
    ~Gc() {
      delete instance_; // 删除单例实例
      instance_ = nullptr; // 将指针置为空
    }
  };

 private:
  static SingletonInstanceLazy* instance_; // 存储唯一的单例实例指针
  static mutex lock_; // 用于保护访问单例实例的互斥锁
  static Gc gc_; // 静态嵌套类实例,用于自动回收单例实例资源
};

// 初始化静态成员变量
SingletonInstanceLazy* SingletonInstanceLazy::instance_ = nullptr;
mutex SingletonInstanceLazy::lock_;

这种模式在第一次调用getInstance()时才会创建实例而不是在程序启动时创建;

通过延迟实例化节省内存,同时加快进程加载速度;

  • 线程安全性

    在单例模式中确保实例化时的线程安全是关键;

    使用mutex互斥锁来保护instance_初始化操作,防止多个线程同时创建多个实例;

  • 双重检查锁定

    getInstance()使用了双重检查锁定机制,首先检查instance_是否为空,如果为空则进入锁定区;

    在锁定区内再次检查instance_是否为空以确保只有一个线程能够成功创建实例;

  • 禁止拷贝和赋值

    构造函数和析构函数被protected访问限定符所给保护;

    拷贝构造函数和赋值重载函数被删除,以防止拷贝构造或赋值重载产生多个实例;

  • 资源自动释放

    内部类Gc用于在成熟结束时自动删除单例实例,通过静态成员变量gc_的析构函数实现;

    当程序结束时,Gc的析构函数会被调用,从而释放SingletonInstanceLazy实例;

一般new的懒汉对象不需要释放,但可能需要进行其他操作例如数据持久化(需要写到文件中),可通过定义的Gc的析构函数来进行实现;


IO流

流 是一种抽象概念,表示数据的有序传输;

流可以从数据源读入数据或将数据写入到数据目标,C++中的流可以分为两类:

  • 输入流

    用于从数据源读取数据;

  • 输出流

    用于将数据写入到数据目标;

C++的IP流系统基于类的层次结构,可以分为以下几类:

  • 基本流类:

    • istream

      基于输入流类,提供了从流中读取数据的基本功能;

    • ostream

      基于输出流类,提供了向流中写入数据的基本功能;

    • iostream

      继承自istreamostream,提供了输入和输出的双向功能;

    其中基本流类都继承自ios类,形成了一个棱形继承,采用虚继承的方式使得iostream具有输入和输出的双向功能;

  • 文件流类

    • ifstream

      继承自istream,用于从文件中读取数据;

    • ofstream

      继承自ostream,用于向文件中写入数据;

    • fstream

      继承自iostream,用于对文件进行输入输出操作;

  • 字符串流类

    • istringstream

      继承自istream,用于从字符串读取数据;

    • ostringstream

      继承自istream,用于向字符串写入数据;

    • stringstream

      继承自iostream,用于对字符串进行输入和输出操作;


类型之间的转换

  • 内置类型转内置类型

    内置类型之间,相近类型可以进行转换,转换的方式一般通过隐式类型转换或是强制类型转换;

    cpp 复制代码
    int main() {
      double a = 10.03;
      int b = a;  // 相近类型隐式类型转换
      cout << a << " : " << b << endl;
      return 0;
    }
    
    /*
    	运行结果为:
    	$ ./mytest 
        10.03 : 10
    */
  • 自定义类型转自定义类型

    自定义类型转自定义类型通过构造函数进行转换;

    cpp 复制代码
    class A {
     public:
      A() {}
      ~A() {}
    
     private:
    };
    
    class B {
     public:
      B(A& a) {}
      ~B() {}
    
     private:
    };
    
    int main() {
      A a;
      B b = a; // 通过构造函数完成自定义类型之间的转换
      return 0;
    }
  • 内置类型转自定义类型

    内置类型转自定义类型同样采用构造函数;

    cpp 复制代码
    class B {
     public:
      B(int a) {}
      ~B() {}
    
     private:
    };
    
    int main() {
      int a = 10;
      B b = a;  // 通过构造函数完成内置类型转自定义类型之间的转换
      return 0;
    }
  • 自定义类型转内置类型

    自定义类型转内置类型可通过operator typename()进行转换;

    cpp 复制代码
    class B {
     public:
      operator int() { return 10; }
    };
    
    int main() {
      B b;
      int i = b;
      cout << i << endl;
      return 0;
    }
    /*
    	运行结果为:
    	$ ./mytest 
        10	
    */
相关推荐
捕鲸叉1 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer1 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq1 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
青花瓷2 小时前
C++__XCode工程中Debug版本库向Release版本库的切换
c++·xcode
幺零九零零4 小时前
【C++】socket套接字编程
linux·服务器·网络·c++
捕鲸叉4 小时前
MVC(Model-View-Controller)模式概述
开发语言·c++·设计模式
Dola_Pan5 小时前
C++算法和竞赛:哈希算法、动态规划DP算法、贪心算法、博弈算法
c++·算法·哈希算法
yanlou2335 小时前
KMP算法,next数组详解(c++)
开发语言·c++·kmp算法
小林熬夜学编程5 小时前
【Linux系统编程】第四十一弹---线程深度解析:从地址空间到多线程实践
linux·c语言·开发语言·c++·算法
阿洵Rain5 小时前
【C++】哈希
数据结构·c++·算法·list·哈希算法