• 特殊类的设计


    一、设计一个类,不能被拷贝

    //1、请设计一个类,不能被拷贝
    // 
    // 拷贝一个类,要么调用拷贝构造函数,要么调用赋值重载函数,所以要令一个类不能
    // 被拷贝,只需要让该类不能调用拷贝构造和赋值重载函数就可以了。
    // 
    // 在C++98语法下,只需要把该类的拷贝构造函数和赋值重载函数声明为私有,
    // 并且不定义即可
    // 
    //原因:拷贝构造函数和赋值重载函数只要我们声明了,编译器就不会再默认生成,但是
    //我们又不定义它,所以这两个函数就不能被调用,如果定义了,反而在类内部会被调用
    // 拷贝构造函数和赋值重载函数,不符合题意;并且我们声明为私有函数,别人想要
    //从类外面自己定义也是做不到的,所以这样的类是不能被拷贝的
    
    class A
    {
    public:
    private:
    	A(const A& a);
    	A& operator=(const A& a);
    };
    
    // C++11语法下:
    // 
    // C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上 = delete,
    // 表示让编译器删除掉该默认成员函数。
    // 所以直接用关键字delete把拷贝构造函数和赋值重载函数删除掉即可
    class A
    {
    private:
    	A(const A& a) = delete;
    	A& operator=(const A& a) = delete;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    二、设计一个类,只能在堆上创建对象

    //2、请设计一个类,只能在堆上创建对象
    // 
    //方法一:要想设计一个类只能在堆上创建对象,只需要将析构函数私有即可
    
    //原因:因为只要定义出对象的,对象是自定义类型,必然需要调用构造
    // 函数和析构函数,而现在把析构函数私有,则定义的对象在销毁的时候
    // 无法调用析构函数,就一定会报错
    //但是如果是在堆上创建对象,返回值是一个对象的指针,指针是内置类型,
    //不会调用构造函数和析构函数,所以即使析构函数是私有的,也可以通过
    //new在堆上创建对象
    class HeapOnly
    {
    public:
    	void func()
    	{
    		cout << "func" << endl;
    	}
    	void Destroy()
    	{
    		//这里delete掉this指针等于是释放了调用
    		//该函数的类的指针,这种写法是正确的
    		delete this;
    		cout << "~HeapOnly()" << endl;
    	}
    
    private:
    	//析构函数私有
    	~HeapOnly()
    	{}
    
    private:
    	int _b;
    };
    
    
    //方法二:把构造函数私有,然后提供一个CreateObj的函数,
    // 这个函数内部用new创建一个对象,然后返回对象的指针即可,
    // 但是要注意把拷贝构造和赋值重载函数delete掉,防止别人
    // 在栈上构造对象
    //
    class HeapOnly
    {
    public:
    	static HeapOnly* CreateObj()
    	{
    		HeapOnly* ptr = new HeapOnly;
    		return ptr;
    	}
    private:
    	HeapOnly()
    	{}
    
    	HeapOnly(const HeapOnly& ho) = delete;
    	HeapOnly& operator=(const HeapOnly& ho) = delete;
    };
    
    int main()
    {
    	//static B b;
    	HeapOnly* pb = new HeapOnly;
    	pb->func();
    	//HeapOnly b(*pb);
    	pb->Destroy();
    
    	return 0;
    }
    
    //int main()
    //{
    //	HeapOnly* ho = HeapOnly::CreateObj();
    //
    //	return 0;
    //}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    三、设计一个类,只能从栈上创建对象

    //3、请设计一个类,只能在栈上创建对象
    //不能完全设计出只在栈上创建对象的类;
    //沿用2的设计思路,把构造函数私有,然后提供一个CreateObj的函数,
    //该函数返回一个在栈上创建的对象。
    //因为这个返回的是临时对象,所以不能把拷贝构造函数和赋值函数delete掉,
    //因为传值返回对象需要被拷贝。
    class StackOnly
    {
    public:
    	static StackOnly CreateObj()
    	{
    		return StackOnly();
    	}
    
    	void func()
    	{
    		cout << "StackOnly()" << endl;
    	}
    
    private:
    	StackOnly()
    	{}
    
    	// 对一个类实现专属operator new,这句代码的意思是把用new
    	//创建该类对象的方法delete掉,外部不能再利用new来创建对象了
    	void* operator new(size_t size) = delete;
    
    };
    
    int main()
    {
    	StackOnly so = StackOnly::CreateObj();
    	cout << &so << endl;
    	int a = 0;
    	cout << &a << endl;
    	so.func();
    
    	//因为用户可能用new调用拷贝构造函数在堆上创建对象,所以需要
    	// 把类的专属的operator new给delete掉,禁止用new在堆上创建对象
    	//StackOnly* pso = new StackOnly(so);
    
    	//这个类唯一不能禁止的就是在静态区创建对象,因为要在栈上创建对象就
    	//一定要有拷贝构造函数,有拷贝构造函数就可以在静态区创建静态对象
    	static StackOnly so1= StackOnly::CreateObj();
    	static int b = 0;
    	cout << &so1 << endl;
    	cout << &b << endl;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    四、设计一个类,不能被继承

    //4. 请设计一个类,不能被继承
    // 
    //C++98语法
    //构造函数私有,然后提供一个CreateObj函数创建对象
    // 原因:构造函数私有,也就意味着构造函数不能被显式地调用,
    // 因为在继承体系中,子类成员中父类的成员必须调用父类的构造函数
    // 初始化父类那一部分成员的,所以如果把父类的构造函数私有,子类
    // 就没有办法调用父类的构造函数初始化父类那一部分成员,所以就继承不了
    //
    class Final
    {
    public:
    private:
    	Final(int f)
    		:_f(f)
    	{}
    
    	int _f = 0;
    };
    
    class A:public Final
    {
    public:
    	//A的构造函数无法调用Final的构造函数,所以Final类不能被继承
    	A()
    		:Final(2)
    		,_a(1)
    	{}
    
    private:
    	int _a;
    };
    //C++11语法:
    //C++11提供了一个关键字final,即直接在类后面加上final表示该类不能被继承
    class Final final
    {
    public:
    	Final(int f)
    		:_f(f)
    	{}
    
    private:
    	int _f = 10;
    };
    
    这里就会报错了,因为Final是不可被继承的
    //class F :public Final
    //{
    //
    //};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    五、设计一个类,只能创建一个对象(单例模式)

    设计模式
    设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
    使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大楼的结构一样。

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

    5.1 饿汉模式

    //1、饿汉模式:一开始(main函数之前)就创建单例对象
    //优点:简单
    //缺点:
    // (1) 如果单例对象要初始化的内容很多,启动速度慢。
    // (2) 如果A单例对象的创建依赖B单例对象,要求B对象先创建好,但是我们无法保证先让B单例对象创建好。
    // (3) 如果单例对象很大,占用资源很多,但是单例对象创建出来之后不是立刻使用,
    //     会占用着大量的内存,导致其它需要内存的地方获取不到内存。
    namespace hungry
    {
    	class Singalton
    	{
    	public:
    		static Singalton& GetInstance()
    		{
    			return _Inst;
    		}
    	private:
    		//构造函数私有,防止别人随意创建对象
    		Singalton()
    		{}
    
    		//把拷贝构造函数和赋值重载函数删除掉,防止拷贝
    		Singalton(const Singalton& sg) = delete;
    		Singalton& operator=(const Singalton& sg) = delete;
    
    		//因为全局只有唯一的一个对象,如何保证我们每次获取到的都是同一个对象呢?
    		// 因为静态的全局变量在main函数之前就会定义好的,所以静态的全局变量是全局唯一的。
    		// 所以这里一般都是在类里面声明一个静态的Singalton对象(类外定义),
    		// 在Singalton类里面可以声明Singalton的静态对象吗?
    		// 声明静态对象static Singalton是可以的,
    		// 但是声明普通对象Singalton就不行,为什么呢?
    		// 因为声明普通对象的话就会出现无限套娃的情况了,对象里面又会套一个对象;但
    		// 是声明静态的Singalton对象为什么就可以了呢?
    		// 因为静态的Singalton对象本身并不存在于Singalton类的空间里面,而是存在于
    		// 静态区中,属于所有对象共有的,所以不存在套娃的情况的,所以可以声明静态的Singalton对象的。
    		// 
    		// 同时要注意,这里只是声明,普通静态对象必须在类外面定义,但是const static对象比较特殊,
    		// 可以在类内定义这样每一次调用GetInstance的时候就返回这个静态的对象就可以保证每次返回的都是
    		// 同一个_Inst了
    		static Singalton _Inst;
     
    		// 在C++中,允许在类内部声明一个本类的静态对象的原因是为了方便和灵活性,
    		//当声明一个类的静态成员时,编译器只需要知道该成员的类型和名称,而不需要
    		//知道成员的具体定义和大小。这样做的好处是可以避免一些循环依赖的问题。如
    		// 果不允许在类内部声明本类的静态对象,那么在类定义之前就无法实例化包含静
    		// 态成员的对象,因为在类定义之前,编译器还不知道该类的完整定义。
    		//通过在类内部声明本类的静态对象,可以为程序提供更高的灵活性。在类定义之后
    		// 的任何位置,都可以在需要的时候进行定义和初始化这个静态对象。这样,程序员
    		// 可以根据需要更具灵活性地控制对象的创建和使用。
    		//需要注意的是,在定义之前使用这个静态对象可能会导致未定义的行为,因此在类
    		// 定义之后的某个地方,一定要进行静态对象的定义和初始化,以确保它的正确使用。
    	};
    
    	//定义,在main函数之前就已经创建好了单例对象
    	Singalton Singalton::_Inst;
    }
    
    
    int main()
    {
    	//每次调用GetInstance获取到的对象都是同一个
    	cout << &hungry::Singalton::GetInstance() << endl;
    	cout << &hungry::Singalton::GetInstance() << endl;
    	cout << &hungry::Singalton::GetInstance() << endl;
    
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

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

    5.2 懒汉模式

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

    //2、懒汉模式
    //比较懒,到有人定义对象的时候才创建对象
    //(1) 优点:第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控制。
    //(2) 缺点:复杂。
    namespace lazy
    {
    	class Singalton
    	{
    	public:
    		static Singalton* GetInstance()
    		{
    			//这里要使用双判断加锁的方式处理,这样才能很好地提高效率
    			
    			//这个if是判断第一次调用实例对象时需要先创建对象
    			if (_pInst == nullptr)
    			{
    				//加锁避免线程安全的问题,两个线程同时进入了这里,需要先竞争锁
    				_pmtx->lock();
    
    				//这个if是判断如果两个进程同时来到了这里,竞争到锁的进程
    				//先来到这个判断条件,如果_pInst还是nullptr,说明这是第一次调用实例对象
    				//此时创建一个Singalton,后竞争到锁的进程在再一次if判断时_pInst就不再是nullptr
    				//了,此时说明单例对象已经存在了,就不会再创建了
    				if (_pInst == nullptr)
    				{
    					_pInst = new Singalton;
    				}
    				_pmtx->unlock();
    			}
    			return _pInst;
    		}
    
    		//一般情况下,单例对象不需要释放,因为程序正常结束就释放了,
    		// 并且单例对象一般也不大,所以可以把析构函数设置成私有
    		//
    		// 但是有些特殊场景:
    		// 1、中途需要显式释放;
    		//2、程序结束时需要做一些持久化(把数据写入到文件中);
    		// 所以需要提供一个显式调用的DelInstance函数(里面封装析构函数)
    		static void DelInstance()
    		{
    			cout << "DelInstance()" << endl;
    			if (_pInst != nullptr)
    			{
    				//自定义对象,调用_pInst对象的析构函数
    				delete _pInst;
    				delete _pmtx;
    			}
    		}
    
    		void Add(const pair<string, string>& val)
    		{
    			_um.insert(val);
    		}
    
    	private:
    		//构造函数
    		Singalton()
    		{}
    
    		//析构函数
    		~Singalton()
    		{
    			//显式调用析构函数书写日志或者持久化(数据写入文件)
    			cout << "~Singalton()" << endl;
    			FILE* fp = fopen("test.txt", "w");
    			for (const auto& e : _um)
    			{
    				fputs(e.first.c_str(), fp);
    				fputs(":", fp);
    				fputs(e.second.c_str(), fp);
    				fputs("\n", fp);
    			}
    		}
    
    		//拷贝构造函数,单例模式防拷贝
    		Singalton(const Singalton& sg) = delete;
    		//赋值重载函数
    		Singalton& operator=(const Singalton& sg) = delete;
    
    		//相当于一个垃圾回收类
    		class GC
    		{
    		public:
    			//用Gc的析构函数管理单例类的析构函数,因为在Singalton中声明了一个Gc的静态的
    			//成员变量,所以在进程结束的时候会调用析构函数,而Gc析构函数又管理着Singalton的
    			// 析构函数DelInstance,所以无论如何进程结束的时候都会自动调用Singalton的析构
    			// 函数的,所以就不存在内存泄漏的隐患了
    			//
    			~GC()
    			{
    				DelInstance();
    			}
    		};
    
    	private:
    		//如何保证每次调用GetInstance函数的时候获取到的_Inst都是同一个呢?因为这里是运行时
    		//才创建对象的,所以不能用静态的对象,而这里要获取到同一个对象,所以只能在堆上开辟;
    		//但是这里为什么是用静态的呢?因为GetInstance是静态的,没有this指针,但是GetInstance
    		//函数中需要用到_Inst,所以需要这个_Inst也要设置为静态的,这样GetInstance函数才能访问_Inst
    		//同理_mtx也要是静态的
    		static Singalton* _pInst;
    		static mutex* _pmtx;
    
    		unordered_map<string, string> _um;
    
    		//在单例类中声明一个Gc类型的静态的成员变量,在类外定义;该对象在进入main函数前就已经创建好了,
    		//到进程结束时才调用析构函数销毁,说明这个变量是整个进程都有效的
    		static GC gc;
    	};
    
    	//必须在类外定义类内的静态成员变量
    	Singalton* Singalton::_pInst = nullptr;
    	mutex* Singalton::_pmtx = new mutex;
    
    	Singalton::GC Singalton::gc;
    }
    
    //GC也可以这样写,这样写更容易理解,用GC创建一个全局对象,进程结束时GC调用析构函数,
    // 进而调用lazy::Singalton::DelInstance()释放单例对象,但是遇到多个单例类就把它们
    // 全部放到~GC函数中即可,上面那种写法就是把GC定义到单例类内部,每一个GC对象管理一个单例类
    //class GC
    //{
    //public:
    //	~GC()
    //	{
    //		lazy::Singalton::DelInstance();
    //	}
    //};
    //GC gc;
    
    int main()
    {
    	//lazy::Singalton* p1 = lazy::Singalton::GetInstance();
    	//lazy::Singalton* p2 = lazy::Singalton::GetInstance();
    	//lazy::Singalton* p3 = lazy::Singalton::GetInstance();
    	//cout << p1 << endl;
    	//cout << p2 << endl;
    	//cout << p3 << endl;
    
    	lazy::Singalton* p1 = lazy::Singalton::GetInstance();
    	p1->Add(make_pair("string", "字符串"));
    	p1->Add(make_pair("left", "左边"));
    	p1->Add(make_pair("right", "右边"));
    
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148

    以上就是常见的特殊类的设计,你学会了吗?今天的分享就到这里啦,如果你感觉到有所收获,那么就点点小心心点点关注呗,后期还会持续更新C++的相关知识哦,我们下期见!!!

  • 相关阅读:
    Vue奶茶冷饮店在线点单系统管理系统 微信小程序
    短视频文案提取的简单实现
    byte buddy字节码增强——输出方法执行时间
    Axure常用技巧及问题
    Vue--keep-alive--详解
    Mac设置终端代理快捷命令
    HIS -- 医院信息管理系统业务流程
    Mysql索引
    信息学奥赛一本通-编程启蒙3304:练51.1 向量点积计算
    Vue3 学习总结笔记 (十三)
  • 原文地址:https://blog.csdn.net/weixin_70056514/article/details/133849530