C++特殊类的设计

1.请设计一个类,不能被拷贝

拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝, 只需让该类不能调用拷贝构造函数以及赋值运算符重载即可

  • C++98

    将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。

    cpp 复制代码
    class CopyBan
    {
    public:
    	CopyBan()//按照需求定义构造函数即可
    	{}
    private:
    	//拷贝构造函数和赋值运算符重载只声明不定义并且将声明放到私有权限下
    	CopyBan(const CopyBan&);
    	CopyBan& operator=(const CopyBan&);
    };

    原因:

    1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了
    2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
  • C++11

    C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上 =delete,表示让编译器删除掉该默认成员函数。

    cpp 复制代码
    class CopyBan
    {
    public:
    	CopyBan()//根据需求设定构造函数
    	{}
    private:
    	//下面这两行放到任意权限下都是可以的
    	CopyBan(const CopyBan&) = delete;
    	CopyBan& operator=(const CopyBan&) = delete;
    };

C++库中经典防拷贝的例子:unique_ptr、thread、mutex、istream、ostream等。

2. 请设计一个类,只能在堆上创建对象

实现方式:

  1. 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
  2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
cpp 复制代码
class HeapOnly
{
public:
	static HeapOnly* CreateObj()
	{
		return new HeapOnly;
	}
	HeapOnly(const HeapOnly&) = delete;//防止通过拷贝构造在栈上建立对象
	//赋值运算符重载就不需要delete,因为赋值是在两个已经存在的对象之间进行赋值,并没有创建对象
private:
	//构造函数私有
	HeapOnly()
	{}
};

当然,还有另外一种设计的方式:

cpp 复制代码
class HeapOnly
{
public:
    //类对象需要自己手动调用析构函数进行析构
	static void DelObj(HeapOnly* ptr)
	{
		delete ptr;
	}
private:
	//析构函数私有
	~HeapOnly()
	{}
};

原理就是在栈和全局区创建对象都要在声明周期结束后自动调用析构函数,析构函数是自动调用的,所以无法在栈和全局区创建对象。

当然,还可以使用另一种方式进行析构对象:

cpp 复制代码
class HeapOnly
{
public:
	void DelObj()
	{
		delete this;
	}
private:
	//析构函数私有
	~HeapOnly()
	{}
};

3. 请设计一个类,只能在栈上创建对象

cpp 复制代码
class StackOnly
{
public:
	static StackOnly CreateObj()
	{
		return StackOnly();
	}
private:
	//构造函数私有
	StackOnly()
	{}
};

或者使用另一种方式:

cpp 复制代码
class StackOnly
{
public:
	void* operator new(size_t size) = delete;
	void operator delete(void* p) = delete;
private:
	//构造函数私有
	StackOnly()
	{}
};

注意:第二种方式是有缺陷的,无法禁掉在全局区定义变量的方式,只禁掉了在堆上定义变量的方式

4. 请设计一个类,不能被继承

  • C++98方式

    cpp 复制代码
    // C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
    class NonInherit
    {
    public:
    	static NonInherit GetInstance()
    	{
    		return NonInherit();
    	}
    private:
    	NonInherit()
    	{}
    };
  • C++11方法

    final关键字,final修饰类,表示该类不能被继承

    cpp 复制代码
    class A final
    {
        //......
    };

5. 请设计一个类,只能创建一个对象(单例模式)

设计模式

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打 仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后 来孙子就总结出了《孙子兵法》。孙子兵法也是类似。

使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

已经接触过的设计模式有迭代器模式适配器模式

单例模式:

一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享(该全局的范围指的就是一个进程中)。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

单例模式有两种实现模式:

  • 饿汉模式

    就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。

    cpp 复制代码
    //饿汉模式
    class Singleton
    {
    public:
    	static Singleton* GetInstance()
    	{
    		return &m_singleton;
    	}
    	void print()
    	{
    		cout << _a << endl;
    	}
    private:
    	//构造函数私有化
    	Singleton(int a)
    		:_a(a)
    	{};
    	//拷贝构造函数和赋值运算符重载删除(C++11的方式,如果要用C++98的方式只需要将其声明为私有但是不定义即可)
    	Singleton(const Singleton&) = delete;
        Singleton& operator=(const Singleton&) = delete;
    private:
    	static Singleton m_singleton;//方式一:声明,定义在类外
    	//static Singleton* m_singleton;//方式二:声明,定义在类外
    	int _a = 0;
    };
    Singleton Singleton::m_singleton(0);//方式一:定义,0是为了初始化_a成员变量
    //Singleton* Singleton::m_singleton = new Singleton(0);//方式二:定义

    如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。

  • 懒汉模式

    如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化, 就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。

    cpp 复制代码
    // 懒汉
    // 优点:第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控制。
    // 缺点:复杂
    class Singleton
    {
    public:
        //需要加锁,如果两个线程同时调用GetInstance就会出现问题
    	static Singleton* GetInstance()
    	{
    		if (m_singleton == nullptr)
    		{
    			m_singleton = new Singleton(0);
    		}
    		return m_singleton;
    	}
    	void print()
    	{
    		cout << _a << endl;
    	}
    private:
    	//构造函数私有化
    	Singleton(int a)
    		:_a(a)
    	{};
    	//拷贝构造函数和赋值运算符重载(C++11的方式,如果是C++98则将其声明在私有权限下但是不进行定义)
    	Singleton(const Singleton&) = delete;
    	Singleton& operator=(const Singleton&) = delete;
    private:
    	static Singleton* m_singleton;huyu
    	int _a = 0;
    };
    Singleton*  Singleton::m_singleton = nullptr;

总结:

饿汉特点:简单、初始化顺序不确定,如果有依赖关系就会有问题。饿汉对象初始化慢且多个饿汉单例对象会影响程序启动。

懒汉特点:复杂(加锁)、初始化顺序可以控制,第一次调用时初始化,可以控制初始化顺序,不影响启动。
问:单例模式的对象是否要考虑内存泄漏的问题?

答:不需要考虑,因为单例模式的生命周期是和进程牢牢绑定在一起的,等进程结束了,空间自然会释放,所以完全不需要担心内存泄漏。

当然,出于严谨的考虑和在一些特殊情况下要在单例对象销毁时进行一些特殊的操作,还是设计出了一个垃圾回收内部类来回收单例模式创建出的对象:

cpp 复制代码
class Singleton
{
public:
	static Singleton* GetInstance()
	{
		if (m_singleton == nullptr)
		{
			m_singleton = new Singleton(0);
		}
		return m_singleton;
	}
	void print()
	{
		cout << _a << endl;
	}
	class CGarbo
	{
	public:
		~CGarbo()
		{
			//析构单例对象
			if (Singleton::m_singleton)
			{
				delete Singleton::m_singleton;
			}
		}
	};
private:
	//构造函数私有化
	Singleton(int a)
		:_a(a)
	{};
	//拷贝构造函数删除
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
private:
	static Singleton* m_singleton;
	//定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象
	static CGarbo m_cgarbo;//声明
	int _a = 0;
};
Singleton*  Singleton::m_singleton = nullptr;
Singleton::CGarbo Singleton::m_cgarbo;//定义
相关推荐
_GR6 分钟前
2022年蓝桥杯第十三届C&C++大学B组真题及代码
c语言·数据结构·c++·算法·蓝桥杯·动态规划
Json_1817901448015 分钟前
python采集淘宝拍立淘按图搜索API接口,json数据示例参考
服务器·前端·数据库
Matlab光学20 分钟前
MATLAB仿真:Ince-Gaussian光束和Ince-Gaussian矢量光束
开发语言·算法·matlab
虾球xz32 分钟前
游戏引擎学习第195天
c++·学习·游戏引擎
珹洺40 分钟前
Java-servlet(十)使用过滤器,请求调度程序和Servlet线程(附带图谱表格更好对比理解)
java·开发语言·前端·hive·hadoop·servlet·html
勘察加熊人44 分钟前
c#使用forms实现helloworld和login登录
开发语言·c#
熙曦Sakura1 小时前
【C++】map
前端·c++
Brandon汐1 小时前
Linux中常用的文件管理命令
linux·运维·服务器
Stardep1 小时前
算法学习11——滑动窗口——最大连续1的个数
数据结构·c++·学习·算法·leetcode·动态规划·牛客网
双叶8361 小时前
(C语言)学生信息表(学生管理系统)(基于通讯录改版)(正式版)(C语言项目)
c语言·开发语言·c++·算法·microsoft