草庐IT

【C++】特殊类设计(单例模式)

命由己造~ 2023-07-05 原文

文章目录

一、设计模式概念

设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。
根本原因是为了代码复用,增加可维护性。

设计模式的例子:迭代器模式

二、设计一个不能被拷贝的类

拷贝一共就只有两个场景,一个是拷贝构造,一个是赋值运算符重载。所以我们想要设计出一个不能被拷贝的类只需要让外部无法调用这两个函数即可。

在C++98中,我们的方法是将拷贝构造和赋值运算符重载只声明不定义并且将权限设置为私有

class anti_copy
{
public:
	anti_copy()
	{}
private:
	anti_copy(const anti_copy& ac);
	anti_copy& operator=(const anti_copy& ac);
};

设计原因:
1️⃣ 私有:如果声明成共有,那么就可以在类外面实现定义。
2️⃣ 只声明不定义:因为如果不声明编译器会默认生成这两个的默认成员函数。而不定义是因为该函数不会被调用,就不用写了,这样编译的时候就会出现链接错误。

而在C++11中引入了关键字——delete。
如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。即使权限是共有也无法调用已删除的函数。

class anti_copy
{
public:
	anti_copy()
	{}
	anti_copy(const anti_copy& ac) = delete;
	anti_copy& operator=(const anti_copy& ac) = delete;
private:
};

三、设计一个只能在堆上创建对象的类

3.1 私有构造

首先要把构造函数给私有,不然这个类就可以在任意位置被创建。而构造函数被私有了以后我们怎么创建对象呢?
我们可以在定义一个成员函数,让这个函数在堆上申请空间,但我们知道必须现有对象才能调用成员函数。所以我们就把这个函数设置成静态成员函数

class OnlyHeap
{
public:
	static OnlyHeap* GetObj()
	{
		return new OnlyHeap;
	}
private:
	OnlyHeap()
	{}
};

但是这样也不完全对,如果我们这么写

class OnlyHeap
{
public:
	static OnlyHeap* GetObj()
	{
		return new OnlyHeap;
	}
private:
	OnlyHeap()
	{}
};

int main()
{
	OnlyHeap* hp1 = OnlyHeap::GetObj();
	OnlyHeap hp2(*hp1);
	return 0;
}

这里的hp2就是栈上的对象。所以我们也要把拷贝构造给封住

class OnlyHeap
{
public:
	static OnlyHeap* GetObj()
	{
		return new OnlyHeap;
	}

	OnlyHeap(const OnlyHeap& hp) = delete;
private:
	OnlyHeap()
	{}
};

3.2 私有析构

class OnlyHeap
{
public:
	OnlyHeap()
	{}

	OnlyHeap(const OnlyHeap& hp) = delete;
private:
	~OnlyHeap()
	{}
};

int main()
{
	OnlyHeap hp1;// error
	OnlyHeap* hp2 = new OnlyHeap;
	return 0;
}

这里的hp1就不能创建成功,因为对象销毁的时候会调用析构函数,但是这里的析构是私有的,所以该对象无法调用

但是我们要销毁hp2该怎么办呢?
我们可以定义一个成员函数显示调用析构函数

class OnlyHeap
{
public:
	OnlyHeap()
	{}

	OnlyHeap(const OnlyHeap& hp) = delete;

	void Destroy()
	{
		this->~OnlyHeap();
	}
private:
	~OnlyHeap()
	{}
};

int main()
{
	OnlyHeap* hp2 = new OnlyHeap;
	hp2->Destroy();
	return 0;
}

四、设计一个只能在栈上创建对象的类

为了不让这个类随便定义出对象,首先要把构造函数私有。然后跟上面只能在堆上创建对象的方法相似,定义出一个静态成员函数返回栈上创建的对象。

class StackOnly
{
public:
	static StackOnly GetObj()
	{
		return StackOnly();
	}
private:
	StackOnly()
	{}
};

int main()
{
	StackOnly hp = StackOnly::GetObj();
	return 0;
}

但是这里有一个问题,无法防止创建静态对象:

static StackOnly hp2 = StackOnly::GetObj();

五、设计不能被继承的类

在C++98,为了不让子类继承,我们可以把构造函数私有化,因为子类需要先调用父类的构造函数初始化父类的那一部分成员。

class NoInherit
{
public:
private:
	NoInherit()
	{}
};

而在C++11中引入的新的关键字final,被final关键字修饰的类不能被继承。

class NoInherit final
{
public:
private:
};

六、单例模式❗️❗️

一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。

单例模式的特点就是全局只有一个唯一对象。

6.1 饿汉模式

怎么能做到全局只是用一个对象呢,比方说我们现在想要实现一个英汉字典,首先我们要把构造函数私有,不然无法阻止创建对象。然后我们可以在类里面定义一个自己类型的静态成员变量,作用域是全局的。因为对比定义在外边的静态成员变量,内部的可以调用构造函数。
这里要注意把拷贝也要封住

class Singleton
{
public:
	static Singleton& GetObj()
	{
		return _s;
	}

	void insert(const std::string& s1, const std::string& s2)
	{
		_dict[s1] = s2;
	}

	void Print()
	{
		for (auto& e : _dict)
		{
			cout << e.first << "->" << e.second << endl;
		}
	}

	// 防拷贝
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
private:
	Singleton()
	{}

	std::map<std::string, std::string> _dict;
private:
	static Singleton _s;// 声明
};

Singleton Singleton::_s;// 定义

int main()
{
	Singleton::GetObj().insert("corn", "玉米");

	Singleton& dic1 = Singleton::GetObj();
	dic1.insert("apple", "苹果");
	dic1.insert("banana", "香蕉");

	Singleton& dic2 = Singleton::GetObj();
	dic2.insert("pear", "梨");

	dic2.Print();
	return 0;
}


饿汉模式有什么特点呢?

它会在一开始(main之前)就创建对象。

饿汉模式有什么缺点呢?

1️⃣ 如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢
2️⃣ 多个单例类之间如果有依赖关系饿汉模式就无法控制,比方说要求A类初始化时必须调用B,但是饿汉无法控制先后顺序

所以针对这些问题,就有了懒汉模式

6.2 懒汉模式

第一次使用实例对象时,创建对象(用的时候创建)。进程启动无负载。多个单例实例启动顺序自由控制。

我们可以直接对上面饿汉模式的代码进行修改,把静态成员变量变成指针。然后把获取的函数改变一下:

static Singleton& GetObj()
	{
		// 第一次调用才会创建对象
		if (_s == nullptr)
		{
			_s = new Singleton;
		}
		return *_s;
	}

整体代码:

class Singleton
{
public:
	static Singleton& GetObj()
	{
		// 第一次调用才会创建对象
		if (_s == nullptr)
		{
			_s = new Singleton;
		}
		return *_s;
	}

	void insert(const std::string& s1, const std::string& s2)
	{
		_dict[s1] = s2;
	}

	void Print()
	{
		for (auto& e : _dict)
		{
			cout << e.first << "->" << e.second << endl;
		}
	}

	// 防拷贝
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
private:
	Singleton()
	{}

	std::map<std::string, std::string> _dict;
private:
	static Singleton* _s;// 声明
};

Singleton* Singleton::_s = nullptr;// 定义

6.2.1 线程安全问题

上面的代码存在问题,当多个线程同时调用GetObj(),就会创建多个对象。所以为了线程安全我们要加锁。为了保证锁自动销毁,我们可以自定义一个锁。

template <class Lock>
class LockAuto
{
public:
	LockAuto(Lock& lk)
		: _lk(lk)
	{
		_lk.lock();
	}

	~LockAuto()
	{
		_lk.unlock();
	}
private:
	Lock& _lk;
};

class Singleton
{
public:
	static Singleton& GetObj()
	{
		// 第一次调用才会创建对象
		if (_s == nullptr)// 只有第一次才用加锁
		{
			LockAuto<mutex> lock(_mutex);
			if (_s == nullptr)
			{
				_s = new Singleton;
			}
		}
		return *_s;
	}

	void insert(const std::string& s1, const std::string& s2)
	{
		_dict[s1] = s2;
	}

	void Print()
	{
		for (auto& e : _dict)
		{
			cout << e.first << "->" << e.second << endl;
		}
	}

	// 防拷贝
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
private:
	Singleton()
	{}

	std::map<std::string, std::string> _dict;
private:
	static Singleton* _s;// 声明
	static mutex _mutex;// 锁
};

Singleton* Singleton::_s = nullptr;// 定义
mutex Singleton::_mutex;// 定义

6.2.2 新写法

class Singleton
{
public:
	static Singleton& GetObj()
	{
		static Singleton dic;
		return dic;
	}

	void insert(const std::string& s1, const std::string& s2)
	{
		_dict[s1] = s2;
	}

	void Print()
	{
		for (auto& e : _dict)
		{
			cout << e.first << "->" << e.second << endl;
		}
	}

	// 防拷贝
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
private:
	Singleton()
	{}

	std::map<std::string, std::string> _dict;
};

这里就用了静态局部变量只会在第一次定义的时候初始化。在C++11之前是不能保证线程安全的,但是C++11之后就可以了。



有关【C++】特殊类设计(单例模式)的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  4. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  5. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  6. ruby-on-rails - 标准化文件名的字符串,删除重音和特殊字符 - 2

    我正在尝试找到一种方法来规范化字符串以将其作为文件名传递。到目前为止我有这个:my_string.mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/n,'').downcase.gsub(/[^a-z]/,'_')但第一个问题:-字符。我猜这个方法还有更多问题。我不控制名称,名称字符串可以有重音符、空格和特殊字符。我想删除所有这些,用相应的字母('é'=>'e')替换重音符号,并将其余的替换为'_'字符。名字是这样的:“Prélèvements-常规”“健康证”...我希望它们像一个没有空格/特殊字符的文件名:“prelevements_routin

  7. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  8. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  9. ruby-on-rails - environment.rb 中设置的常量在开发模式中消失 - 2

    了解Rails缓存如何工作的人可以真正帮助我。这是嵌套在Rails::Initializer.runblock中的代码:config.after_initializedoSomeClass.const_set'SOME_CONST','SOME_VAL'end现在,如果我运行script/server并发出请求,一切都很好。然而,在我的Rails应用程序的第二个请求中,一切都因单元化常量错误而变得糟糕。在生产模式下,我可以成功发出第二个请求,这意味着常量仍然存在。我已通过将以上内容更改为以下内容来解决问题:config.after_initializedorequire'some_cl

  10. ruby-on-rails - 设计注册确认 - 2

    我在我的项目中有一个用户和一个管理员角色。我使用Devise创建了身份验证。在我的管理员角色中,我没有任何确认。在我的用户模型中,我有以下内容:devise:database_authenticatable,:confirmable,:recoverable,:rememberable,:trackable,:validatable,:timeoutable,:registerable#Setupaccessible(orprotected)attributesforyourmodelattr_accessible:email,:username,:prename,:surname,:

随机推荐