单例模式C++实现

特点

保证类只有一个实例对象,且提供一个访问方法,自行生成实例
个人认为不需要析构函数,这个对象生成后虽然在堆,但指向它的指针是静态的,静态就是对象是属于类的,生成到程序结束就可以一直存在,不会内存泄漏

  • 所有实现方式都要注意的点
    • 私有构造函数不允许用户创建对象
    • 不允许复制对象

最简单的懒汉式

class Singleton {
private:
	Singleton() {
		cout << "构造函数" << endl;
	}
	Singleton(const Singleton&);
	Singleton& operator=(const Singleton&);
	static Singleton* m_instance;
public:
	static Singleton* getInstance();
};
Singleton* Singleton::m_instance = nullptr;

Singleton* Singleton::getInstance() {
	if (m_instance == nullptr) {
		m_instance = new Singleton;
	}
	return m_instance;
}
int main() {
	auto tmp = Singleton::getInstance();
	return 0;
}

问题:

  1. 多线程情况下不能保证线程安全

线程安全的懒汉式

class Singleton {
private:
	Singleton() {
		cout << "构造函数" << endl;
	}
	Singleton(const Singleton&);
	Singleton& operator=(const Singleton&);
	static Singleton* m_instance;
public:
	static Singleton* getInstance();
	static mutex mtx;
};
Singleton* Singleton::m_instance = nullptr;
mutex Singleton::mtx;

Singleton* Singleton::getInstance() {
	lock_guard<mutex> lg(mtx);
	if (m_instance == nullptr) {
		m_instance = new Singleton;
	}
	return m_instance;
}

int main() {
	auto tmp = Singleton::getInstance();
	return 0;
}

问题:

  1. 这个锁在第一次创建完对象之后就没用了,造成多余的开销

双检锁懒汉式

class Singleton {
private:
	Singleton() {
		cout << "构造函数" << endl;
	}
	Singleton(const Singleton&);
	Singleton& operator=(const Singleton&);
	static Singleton* m_instance;
public:
	static Singleton* getInstance();
	static mutex mtx;
};
Singleton* Singleton::m_instance = nullptr;
mutex Singleton::mtx;

Singleton* Singleton::getInstance() {
	if (m_instance == nullptr) {
		lock_guard<mutex> lg(mtx);
		if (m_instance == nullptr) {
			m_instance = new Singleton;
		}
	}
	return m_instance;
}

int main() {
	auto tmp = Singleton::getInstance();
	return 0;
}

问题:

  1. 锁前不检查,就是上一种情况,多余开销
  2. 锁后不检查,lock之后和第一次创建对象之前这段时间,多线程进入第一个if判断会出现问题
  3. CPU可能对指令进行重排
  4. 执行的逻辑顺序:new分配内存-->构造函数-->赋值
  5. 执行的可能顺序:new分配内存-->赋值-->构造函数
  6. 这时候如果有一个线程在第一个if判断,就会返回一个没有调用构造函数的内存

最简洁的写法,懒汉式

class Singleton {
private:
	Singleton() {
		cout << "构造函数" << endl;
	}
	Singleton(const Singleton&);
	Singleton& operator=(const Singleton&);
	static Singleton* m_instance;
public:
	static Singleton& getInstance();
};

Singleton& Singleton::getInstance() {
	static Singleton m_instance;
	return m_instance;
}

int main() {
	auto& tmp = Singleton::getInstance();
	return 0;
}

注意:

  1. 返回值必须赋值给引用变量,因为两种赋值操作都是不可用的
  2. 函数返回值是引用,不是指针,避免用户delete。singleton的生存期应该由类本身自己管理,饥汉式懒汉式

饥汉式

class Singleton {
private:
	Singleton() {
		cout << "构造函数" << endl;
	}
	Singleton(const Singleton&);
	Singleton& operator=(const Singleton&);
	static Singleton* m_instance;
public:
	static Singleton* getInstance();
};
Singleton* Singleton::m_instance = new Singleton;

Singleton* Singleton::getInstance() {
	return m_instance;
}

int main() {
	auto tmp = Singleton::getInstance();
	return 0;
}

注意:

  1. 饥汉式是程序一运行就创建对象
  2. 懒汉式是需要使用类对象实例才去创建对象
  3. 如何选择:饥汉式是空间换时间,避免需要时的创建时间开销;懒汉式是时间换空间,需要的时候在创建出来

应用场景及优缺点

注意:这里的线程安全只是指getInstance函数

优点:

  1. 就是单例的特点咯,只提供一个实例
  2. 类似包装了一个全局变量,但是它可以懒汉式延迟生成,
  3. 减少内存开销

缺点:

  1. 锁和判断产生开销,不过可以通过静态方式解决
  2. 违背单一职责原则(一个类只有一个引起变化的原因)

应用场景:利用到单例的这种思想

  1. 进程管理器
  2. 微信
  3. 项目中的管理器,我就只需要一份全局的实例管理,比如线程池,项目中也有用到一个全局管理器,可以换成单例模式实现

面向对象原则

  1. 单一职责原则
    每个类专注做一件事
    例子:类不会有太多方法、成员,更容易维护

  2. 里氏替换原则
    子类必须能替换父类(is-a)

  3. 依赖倒置原则
    高层模块不应该依赖于低层模块,而应该依赖于抽象。抽象不应依赖于细节,细节应依赖于抽象

  4. 接口隔离原则
    为用户提供小接口,不提供大的总的接口
    例子:方法该private就private,该protected就protected,暴露用户不需要的方法,用户产生依赖就不方便修改了

  5. 开放封闭原则
    对拓展开放,对修改封闭

应用

  1. 隔离变化
  2. 各负其责(多态)

为什么要面向对象

  1. 需求变化很快,需要更改代码,可复用
  2. 实现形式就是封装继承多态,利用这种实现形式但是并不代表你的代码就是面向对象的
posted @ 2021-03-15 00:53  肥斯大只仔  阅读(107)  评论(0编辑  收藏  举报