文章目录
- 单例模式
- 饿汉加载的单例模式实现
- 懒汉加载的单例模式实现
- IO流
- 类型之间的转换
单例模式
单例模式是一种创建型设计模式;
确保一个类在应用程序的生命周期内仅有一个实例并提供一个全局访问点来访问该实例;
单例模式主要目的是为了控制某些类的实例化以避免产生多个实例,从而节省资源和避免数据不一致问题;
单例模式的核心要点为如下:
-
唯一性
单例模式确保一个类只有一个实例,意味着该类的所有对象共享相同的状态和行为;
-
全局访问点
单例模式提供了一个静态方法(
getInstance()
),使得客户端可通过这个方法访问唯一的实例而不需要创建对象; -
加载方式
单例模式可通过需求来自定义需要的加载方式,常见的加载方式为饿汉加载懒汉加载;
-
饿汉加载
饿汉加载指的是单例模式在进程创建时就对单例的资源进行初始化,从而间接提高运行的速度;
因为进程启动时单例就已经被初始化意味着不需要再花时间对该单例进行初始化操作;
相同的由于初始化的时机是在进程启动时,所以饿汉加载方式的启动速度要较慢;
饿汉加载是线程安全的,但饿汉模式加载处的实例若是没有被使用则是一种空间的浪费的行为;
尽管饿汉加载是线程安全的,也只是代表在加载过程中是安全的,若是实例中存在可能出现资源竞争的临界资源时同样必须为该单例考虑同步互斥问题;
-
懒汉加载
懒汉加载是单例的一种加载模式,懒汉加载模式旨在需要时对实例进初始化加载,从而提高进程的加载速度;
由于懒汉加载模式是在需要时对实例进行加载,这意味着不需要花费时间在进程启动时对实例进行资源的加载;
与饿汉模式不同,懒汉加载不是线程安全的,懒汉加载模式涉及到当需要该实例时多个线程同时调用加载函数对实例进行初始化加载,故在设计懒汉加载时需要考虑多线程并发情况下线程的同步与互斥问题;
与饿汉模式不同的是懒汉模式不存在"一定要加载,不一定使用"的问题所引发的资源浪费的可能;
-
单例模式的实现步骤一般为:
-
私有化构造函数
通过私有化构造函数防止类外代码随意对实例进行控制从而可能产生多个实例;
-
静态私有成员变量
在类中声名一个静态的私有成员变量,用于存储该类的唯一实例,这个成员变量可以是一个对象也可以是一个指针变量,取决于加载方式;
-
静态公有方法
在单例模式中会提供一个静态共有方法(通常命名为
getInstance()
)来获取该类的唯一实例;
饿汉加载的单例模式实现
// 单例类的定义
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
指针,但其有权访问该类中的所有成员;
私有化构造函数以确保类外部无法创建或销毁单例实例从而保持单例模式的完整性;
通过删除拷贝构造和赋值重载防止通过赋值或拷贝的方式创建新的实例从而进一步保证单例模式的唯一性;
懒汉加载的单例模式实现
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
继承自
istream
和ostream
,提供了输入和输出的双向功能;
其中基本流类都继承自
ios
类,形成了一个棱形继承,采用虚继承的方式使得iostream
具有输入和输出的双向功能; -
-
文件流类
-
ifstream
继承自
istream
,用于从文件中读取数据; -
ofstream
继承自
ostream
,用于向文件中写入数据; -
fstream
继承自
iostream
,用于对文件进行输入输出操作;
-
-
字符串流类
-
istringstream
继承自
istream
,用于从字符串读取数据; -
ostringstream
继承自
istream
,用于向字符串写入数据; -
stringstream
继承自
iostream
,用于对字符串进行输入和输出操作;
-
类型之间的转换
-
内置类型转内置类型
内置类型之间,相近类型可以进行转换,转换的方式一般通过隐式类型转换或是强制类型转换;
int main() { double a = 10.03; int b = a; // 相近类型隐式类型转换 cout << a << " : " << b << endl; return 0; } /* 运行结果为: $ ./mytest 10.03 : 10 */
-
自定义类型转自定义类型
自定义类型转自定义类型通过构造函数进行转换;
class A { public: A() {} ~A() {} private: }; class B { public: B(A& a) {} ~B() {} private: }; int main() { A a; B b = a; // 通过构造函数完成自定义类型之间的转换 return 0; }
-
内置类型转自定义类型
内置类型转自定义类型同样采用构造函数;
class B { public: B(int a) {} ~B() {} private: }; int main() { int a = 10; B b = a; // 通过构造函数完成内置类型转自定义类型之间的转换 return 0; }
-
自定义类型转内置类型
自定义类型转内置类型可通过
operator typename()
进行转换;class B { public: operator int() { return 10; } }; int main() { B b; int i = b; cout << i << endl; return 0; } /* 运行结果为: $ ./mytest 10 */