C++特殊类设计概念与示例讲解

 更新时间:2023年04月22日 11:27:51   作者:命由己造~  
本文介绍C++中三种特殊类设计模式:单例模式、工厂模式和代理模式。通过详细讲解每种设计模式的实现原理和应用场景,帮助读者理解和掌握这些常用的面向对象设计模式,并提供示例代码和技巧,便于实际应用

一、设计模式概念

设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。

使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。

根本原因是为了代码复用,增加可维护性。

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

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

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

在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++特殊类设计概念与示例讲解的文章就介绍到这了,更多相关C++特殊类设计内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 浅谈C++11的std::function源码解析

    浅谈C++11的std::function源码解析

    类模版std::function是一种通用的多态函数包装器std::function的实例可以对任何可以调用的目标实体进行存储、复制、和调用操作,本文详细的介绍一下,感兴趣的可以了解一下
    2021-06-06
  • linux c多线程编程实例代码

    linux c多线程编程实例代码

    这篇文章主要介绍了linux系统中的c多线程编程实例,大家可以参考使用以下代码
    2013-11-11
  • C语言实现随机抽取纸牌程序

    C语言实现随机抽取纸牌程序

    这篇文章主要为大家详细介绍了C语言实现随机抽取纸牌程序,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • C++ pair的基本用法总结整理

    C++ pair的基本用法总结整理

    这篇文章主要介绍了C++ pair的基本用法总结整理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • C++中 Sort函数详细解析

    C++中 Sort函数详细解析

    这篇文章主要介绍了C++中 Sort函数详细解析,sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变
    2022-08-08
  • DLL加载设置相对路径的方法

    DLL加载设置相对路径的方法

    这篇文章给大家介绍了DLL加载设置相对路径的方法,非常不错,具有一定的参考借鉴加载,需要的朋友参考下吧
    2018-08-08
  • C++中如何调用C语言的代码实现

    C++中如何调用C语言的代码实现

    这篇文章主要介绍了C++中如何调用C语言的代码实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-03-03
  • C++使用GDAL库实现Tiff文件的读取

    C++使用GDAL库实现Tiff文件的读取

    这篇文章主要为大家详细介绍了C++使用GDAL库实现Tiff文件的读取的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-03-03
  • 一篇文章让你彻底明白c++11增加的变参数模板

    一篇文章让你彻底明白c++11增加的变参数模板

    C++11的新特性--可变模版参数(variadic templates)是C++11新增的最强大的特性之一,它对参数进行了高度泛化,它能表示0到任意个数、任意类型的参数,这篇文章主要给大家详细介绍了关于c++11增加的变参数模板的相关资料,需要的朋友可以参考下
    2021-08-08
  • opencv3/C++ 离散余弦变换DCT方式

    opencv3/C++ 离散余弦变换DCT方式

    今天小编就为大家分享一篇opencv3/C++ 离散余弦变换DCT方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-12-12

最新评论