目录

  • 一、请设计一个类,不能被拷贝
  • 二、请设计一个类,只能在堆上创建对象
  • 三、请设计一个类,只能在栈上创建对象
  • 四、请设计一个类,不能被继承
  • 五、请设计一个类,只能创建一个对象(单例模式)
  • 5.1 饿汉模式
  • 5.2 懒汉模式
  • 结尾
  • 一、请设计一个类,不能被拷贝

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

    1. C++98

      私有 + 只声明不定义

    2. 私有:若只声明不定义并且在没有私有的情况下,用户可以在外面进行定义
    3. 只声明不定义:不声明,操作系统会生成默认的拷贝构造和拷贝复制函数,定义了就不能防止拷贝了。
    4. C++11

      在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。

    class BanCopy
    {
    public:
    	// 默认构造函数
    	BanCopy()
    	{}
    
    	// C++11 默认成员函数后跟上 = delete
    	// 拷贝构造
    	BanCopy(const BanCopy& bc) = delete;
    	// 拷贝赋值
    	BanCopy& operator=(const BanCopy& bc) = delete;
    
    private:
    	// C++ 98 私有 + 只声明不定义
    	// 若只声明不定义并且在没有私有的情况下,用户可以在外面进行定义
    	/*BanCopy(const BanCopy& bc);
    	BanCopy operator=(const BanCopy& bc);*/
    };
    
    int main()
    {
    	BanCopy bc1;
    	BanCopy bc2;
    
    	BanCopy bc3(bc1);
    	bc2 = bc1;
    
    	return 0;
    }
    

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

    实现方式1:

    1. 构造函数只声明不定义并私有化,拷贝构造函数只声明不定义并私有化,防止通过拷贝构造在栈上创建对象。
    2. 定义一个静态函数,用来提供在堆上创建对象。
    class OnlyHeap
    {
    public:
    	static OnlyHeap* CreateObject()
    	{
    		return new OnlyHeap;
    	}
    
    private:
    	// 默认构造
    	OnlyHeap()
    	{}
    
    	// 拷贝构造
    	OnlyHeap(const OnlyHeap& oh)
    	{}
    };
    
    int main()
    {
    	OnlyHeap oh1;
    
    	OnlyHeap* oh2 = OnlyHeap::CreateObject();
    
    	OnlyHeap oh3(*oh2);
    
    	return 0;
    }
    

    实现方式2:

    析构函数只声明不定义并私有化,析构函数是私有的,那么在对象离开其作用域时,编译器试图调用析构函数时会遇到问题,因为它不能从外部访问私有成员。

    class OnlyHeap
    {
    public:
    	// 默认构造
    	OnlyHeap()
    	{}
    
    	static OnlyHeap* CreateObject()
    	{
    		return new OnlyHeap;
    	}
    
    private:
    	// 拷贝构造
    	OnlyHeap(const OnlyHeap& oh)
    	{}
    
    	~OnlyHeap()
    	{}
    };
    
    int main()
    {
    	// OnlyHeap oh1;
    
    	OnlyHeap* oh2 = OnlyHeap::CreateObject();
    
    	OnlyHeap oh3(*oh2);
    
    	return 0;
    }
    

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

    class OnlyStack
    {
    public:
    	static OnlyStack CreateStack()
    	{
    		OnlyStack os;
    		return os;
    	}
    
    
    	// 这里不能将拷贝构造删除
    	// 因为CreateStack函数是传值返回,需要拷贝构造
    	// OnlyStack(const OnlyStack& os) = delete;
    private:
    	OnlyStack()
    	{}
    
    	// new分为构造和operator new 两个部分
    	// 我们已经对构造函数动手了,拷贝构造又不能动
    	// 那么接下来只有对operator new动手了
    
    	// 实现专属的operator new
    	// 那么new这个对象的时候就不会调用全局的,而是调用这里的
    	/*void* operator new(size_t size)
    	{
    		cout << "void* operator new(size_t)" << endl;
    		return malloc(size);
    	}*/
    
    	// 我们将operator new删除了,那么就new不了对象了
    	void* operator new(size_t) = delete;
    };
    
    int main()
    {
    	OnlyStack os1 = OnlyStack::CreateStack();
    
    	// OnlyStack* os2 = new OnlyStack;
    
    	OnlyStack* os3;
    
    	os3 = new OnlyStack(os1);
    
    	return 0;
    }
    

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

    1. C++98
      由于派生类的构造需要调用基类的构造函数,而这里将构造函数私有后,派生类则不能调用基类的构造函数,那么该类不能被继承。
    class CannotInherit
    {
    public:
    	static CannotInherit CreateObject()
    	{
    		return CannotInherit();
    	}
    
    private:
    	CannotInherit()
    	{}
    };
    
    class DerivedClass : public CannotInherit
    {
    	DerivedClass()
    	{}
    };
    

    1. C++11
      final修饰类,表示该类不能被继承。

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

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

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

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

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

    5.1 饿汉模式

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

    饿汉模式的优缺点

  • 优点:简单
  • 缺点:可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定。
  • // 饿汉模式程序启动时就创建一个唯一的实例对象
    // 那么什么变量能够在进入main函数之前就定义呢
    // 那么就是全局变量了
    // 但是下面单例模式中的构造函数私有化了
    // 导致外面的无法构造对象了
    // 全局变量不行,那么就可以使用静态变量了
    // 在单例模式中添加一个该类的静态类对象
    // 在类中声明,在类外定义
    
    class SingLeton
    {
    	static SingLeton* GetInstance()
    	{
    		return &_sl;
    	}
    
    private:
    	// 私有化构造函数
    	SingLeton()
    	{}
    
    	// 防拷贝
    	// 删除拷贝构造
    	SingLeton(const SingLeton& sl) = delete;
    
    	// 删除拷贝赋值
    	SingLeton& operator=(const SingLeton& sl) = delete;
    
    private:
    	static SingLeton _sl;
    };
    
    SingLeton SingLeton::_sl;
    

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

    那么这里单例模式中的饿汉模式就完成了,需要某个资源只创建一个对象,那么就在单例模式中添加这个资源的成员变量即可。


    5.2 懒汉模式

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

    懒汉模式的优缺点

  • 优点:第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控制。
  • 缺点:复杂
  • 假设下面懒汉模式下存储的对象是map

    // 懒汉模式第一次使用实例对象时,创建对象
    // 所以我们在类里面定义一个该类的静态指针类型
    // 再在类外面对该指针初始化为nullptr
    // 当第一个使用一个类时,再创建对象
    class SingLeton
    {
    public:
    	static SingLeton* GetInstance()
    	{
    		if (_psl == nullptr)
    		{
    			_psl = new SingLeton;
    		}
    
    		return _psl;
    	}
    
    	void Insert(const string& key, const string& value)
    	{
    		_dict[key] = value;
    	}
    
    	void Print()
    	{
    		for (auto dict : _dict)
    		{
    			cout << dict.first << ':' << dict.second << endl;
    		}
    		cout << endl;
    	}
    
    private:
    	// 私有化构造函数
    	SingLeton()
    	{}
    
    private:
    	static SingLeton* _psl;
    
    	map<string, string> _dict;
    
    };
    
    SingLeton* SingLeton::_psl = nullptr;
    

    那么为什么需要删除拷贝构造和拷贝赋值呢?

    int main()
    {
    	SingLeton::GetInstance()->Insert("want","想");
    	SingLeton::GetInstance()->Insert("miss", "错过");
    	SingLeton::GetInstance()->Insert("left", "左边");
    
    	SingLeton p(*(SingLeton::GetInstance()));
    	p.Insert("miss", "想念");
    	SingLeton::GetInstance()->Print();
    	p.Print();
    
    	return 0;
    }
    


    因为不删除拷贝构造和拷贝赋值会导致单例不具有唯一性。

    删除拷贝构造和拷贝赋值后的懒汉模式。

    // 懒汉模式第一次使用实例对象时,创建对象
    class SingLeton
    {
    public:
    	static SingLeton* GetInstance()
    	{
    		if (_psl == nullptr)
    		{
    			_psl = new SingLeton;
    		}
    
    		return _psl;
    	}
    
    	void Insert(const string& key, const string& value)
    	{
    		_dict.insert(make_pair(key, value));
    	}
    
    	void Print()
    	{
    		for (auto dict : _dict)
    		{
    			cout << dict.first << ':' << dict.second << endl;
    		}
    	}
    
    private:
    	// 私有化构造函数
    	SingLeton()
    	{}
    
    	// 防拷贝
    	// 删除拷贝构造
    	SingLeton(const SingLeton& sl) = delete;
    
    	// 删除拷贝赋值
    	SingLeton& operator=(const SingLeton& sl) = delete;
    
    private:
    	static SingLeton* _psl;
    
    	map<string, string> _dict;
    };
    

    依照上面的代码还可以看出一个问题,那就是_psl是new出来的,需要delete吗?如何delete呢?

    一般来说单例模式是伴随着一整个程序的,程序结束后会自动释放,不排除单例模式用到一半后不需要它了的情况,也有可能是程序结束后需要对数据进行持久化,所以可能需要delete,那么如何delete呢?

    首先可以想到这个delete SingLeton::GetInstance(),但是这个写法太挫了,别人可能看漏或是不理解,在后面继续使用但是模式,那么下面这段代码教大家如何delete这段数据。

    首先在SingLeton中定义一个函数static void DelInstance()用来释放空间,再定义一个内部类InstanceCleaner,而这个类是空类,在SingLeton中声明一个静态的InstanceCleaner类对象,在类外面定义,由于是空类,并不用担心会影响程序启动的速度,在InstanceCleaner的析构函数中调用DelInstance,那么在程序结束后会释放InstanceCleaner对象,调用析构函数,再调用DelInstance释放数据。值得注意的是SingLeton对象中的数据可以在程序结束后,依靠InstanceCleaner来释放数据,也可以自己手动调用DelInstance提前释放数据。

    class SingLeton
    {
    public:
    	static SingLeton* GetInstance()
    	{
    		if (_psl == nullptr)
    		{
    			_psl = new SingLeton;
    		}
    
    		return _psl;
    	}
    
    	static void DelInstance()
    	{
    		if (_psl)
    		{
    			delete _psl;
    			_psl = nullptr;
    			cout << "static void DelInstance()" << endl;
    		}
    	}
    
    	void Insert(const string& key, const string& value)
    	{
    		_dict.insert(make_pair(key, value));
    	}
    
    	void Print()
    	{
    		for (auto dict : _dict)
    		{
    			cout << dict.first << ':' << dict.second << endl;
    		}
    	}
    
    	class InstanceCleaner
    	{
    	public:
    		InstanceCleaner()
    		{}
    
    		~InstanceCleaner()
    		{
    			DelInstance();
    		}
    	};
    
    private:
    	// 私有化构造函数
    	SingLeton()
    	{}
    
    	~SingLeton()
    	{}
    
    	// 防拷贝
    	// 删除拷贝构造
    	SingLeton(const SingLeton& sl) = delete;
    
    	// 删除拷贝赋值
    	SingLeton& operator=(const SingLeton& sl) = delete;
    private:
    	static SingLeton* _psl;
    
    	map<string, string> _dict;
    
    	static InstanceCleaner _InstanceCleaner;
    };
    
    SingLeton* SingLeton::_psl = nullptr;
    SingLeton::InstanceCleaner _InstanceCleaner;
    
    int main()
    {
    	SingLeton::GetInstance()->Insert("want","想");
    	SingLeton::GetInstance()->Insert("miss", "错过");
    	SingLeton::GetInstance()->Insert("left", "左边");
    
    	cout << "Hello C++" << endl;
    	SingLeton::GetInstance()->DelInstance();
    
    	return 0;
    }
    


    实际上上面的懒汉模式还存在线程安全问题,在创建和删除单例的时候,多个线程可能会同时进入,这里我们保证只有第一个调用的线程才可以创建或是删除的单例。

    // 懒汉模式第一次使用实例对象时,创建对象
    #include <mutex>
    class SingLeton
    {
    public:
    	static SingLeton* GetInstance()
    	{
    		// 防止浪费锁资源
    		if (_psl == nullptr)
    		{
    			// 防止多个线程同时进入
    			unique_lock<mutex> lock(mtx);
    			if (_psl == nullptr)
    			{
    				_psl = new SingLeton;
    			}
    		}
    		return _psl;
    	}
    
    	static void DelInstance()
    	{
    		if (_psl)
    		{
    			unique_lock<mutex> lock(mtx);
    			if (_psl)
    			{
    				delete _psl;
    				_psl = nullptr;
    				cout << "static void DelInstance()" << endl;
    			}
    		}
    	}
    
    	void Insert(const string& key, const string& value)
    	{
    		_dict.insert(make_pair(key, value));
    	}
    
    	void Print()
    	{
    		for (auto dict : _dict)
    		{
    			cout << dict.first << ':' << dict.second << endl;
    		}
    	}
    
    	class InstanceCleaner
    	{
    	public:
    		InstanceCleaner()
    		{}
    
    		~InstanceCleaner()
    		{
    			DelInstance();
    		}
    	};
    
    private:
    	// 私有化构造函数
    	SingLeton()
    	{}
    
    	~SingLeton()
    	{}
    
    	// 防拷贝
    	// 删除拷贝构造
    	SingLeton(const SingLeton& sl) = delete;
    
    	// 删除拷贝赋值
    	SingLeton& operator=(const SingLeton& sl) = delete;
    private:
    	static SingLeton* _psl;
    	static mutex mtx;
    
    	map<string, string> _dict;
    
    	static InstanceCleaner _InstanceCleaner;
    };
    
    SingLeton* SingLeton::_psl = nullptr;
    SingLeton::InstanceCleaner _InstanceCleaner;
    

    这里我们设计一个最简单的单例模式,下面的并且下面的代码是懒汉模式,只有第一次调用函数时,才会创建单例。sl是一个静态对象,只有在第一次调用的时候才会初始化,下面的代码在C++11之前是存在线程安全问题的,这也是C++11之前的缺陷,而C++11之后就不存在线程安全问题了,保证了sl只初始化一次。

    class SingLeton
    {
    public:
    	static SingLeton& GetInstance()
    	{
    		static SingLeton sl;
    		return sl;
    	}
    
    private:
    	// 私有化构造函数
    	SingLeton()
    	{}
    
    	~SingLeton()
    	{}
    
    	// 防拷贝
    	// 删除拷贝构造
    	SingLeton(const SingLeton& sl) = delete;
    
    	// 删除拷贝赋值
    	SingLeton& operator=(const SingLeton& sl) = delete;
    };
    

    结尾

    如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。
    希望大家以后也能和我一起进步!!🌹🌹
    如果这篇文章对你有用的话,希望大家给一个三连支持一下!!🌹🌹

    作者:是阿建吖!

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【C++】特殊类设计

    发表回复