C++基础(二十三):特殊类的设计

       这一节主要是对前面学习的类和对象的一个应用,考查对于类和对象基本概念的理解,学会掌握常见特殊类的设计方式,可以加深我们对于类和对象的理解。

目录

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

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

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

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

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

5.1 设计模式

5.2 单例模式

5.2.1 饿汉模式

5.2.2 懒汉模式

5.2.3  饿汉和懒汉模式的区别


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

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

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


class CopyBan
{
	// ...

private:
	CopyBan(const CopyBan&);
	CopyBan& operator=(const CopyBan&);
	//...
};

原因: 1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,它还是可以在类外进行拷贝构造,这样就会失去效果! 2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。

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

class CopyBan
{
	// ...
	CopyBan(const CopyBan&) = delete;
	CopyBan& operator=(const CopyBan&) = delete;
	//...
};

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

实现方式:

  1. 将类的构造函数私有化,拷贝构造声明成私有不实现。防止别人调用拷贝在栈上生成对象。
  2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建。
#include <iostream>
using namespace std;

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


/*思路分析:正常创建出来的对象一定要调用构造函数或者拷贝构造函数,那么我们是不是就可以让构造函数私有化?
但是这样存在缺点:不论是栈上创建出的对象
还是堆上创建出的对象都无法调用构造函数(私有函数无法被外界调用),如何解决?
那么我们是不是可以实现一个成员函数来是实现堆上创建出对象,答案是可以的,
并且需要是静态的成员函数,因为普通成员函数是需要通过对象调用的,
而我们还没有对象无法调用,
静态的成员函数属于这个类,我们可以直接使用类来调用!
但是存在另一个问题:它可以调用拷贝构造创建出对象!
将拷贝构造设置成私有只声明不实现!


************************************************************************************/
                       /*总结*/
//1. 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
//2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建


class HeapOnly
{

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

private:
	HeapOnly()
	{
	}

	//C++98做法
    // 声明成私有,只声明,不实现。因为我不让你拷贝构造通过!
	HeapOnly(const HeapOnly&);

	// C++11做法   
	HeapOnly(const HeapOnly&) = delete;

};

int main()
{
	 //HeapOnly hp;                      无法通过!
	//HeapOnly* p=new HeapOnly;         无法通过!

	HeapOnly* p = HeapOnly::GetObj();
	std::shared_ptr<HeapOnly> sp1(HeapOnly::GetObj());  //防止内存泄漏

	//HeapOnly hp1(*sp1);      //这里会调用拷贝构造!创建出栈上的对象==>将拷贝构造设置成私有只声明不实现

	return 0;
}

/******************************************************************************************/

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

同上将构造函数私有化,然后设计静态方法创建对象返回即可。

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


class StackOnly
{

public:

	static StackOnly GetObj()
	{
		return  StackOnly();                   //传值返回这里会调用拷贝构造
	}


private:
	StackOnly()
	{
	}


};

int main()
{
	StackOnly so = StackOnly::GetObj();

   //StackOnly * p = new StackOnly;         无法通过!new会调用构造函数,但是无法调用,此时构造函数为私有的!

	return 0;
}


/*******************************************************************************/

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

C++98方式:构造函数私有化,派生类中调不到基类的构造函数。则无法继承

/**************************请设计一个类,不能被继承 *******************************/

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

/*******************************************************************************/

C++11方法 :final关键字,final修饰类,表示该类不能被继承。

/**************************请设计一个类,不能被继承 *******************************/

class A  final
{
	// ....
};

/*******************************************************************************/

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

5.1 设计模式

      设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的 总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打 仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后 来孙子就总结出了《孙子兵法》。孙子兵法也是类似。 使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。之前已经学过一些设计模式,迭代器模式--基于面向对象三大特性之一的封装设计出来的,用一个迭代器类封装以后,不暴露容器的结构的情况下,统一的方式访问修改容器中的数据。适配器模式--体现的是一种复用,还有一些常见的设计模式如:工厂模式、装饰器模式、观察者模式、单例模式...

5.2 单例模式

       一个类只能在全局(进程)实例化出一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。再比如:一个进程中有一个内存池,进程中的多线程需要内存都要到这个内存池中取,那么这个内存池的类就可以设计成单例模式。单例模式有两种实现模式:饿汉模式和懒汉模式。

5.2.1 饿汉模式

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

#include <iostream>
using namespace std;



// 饿汉模式
// 优点:简单
// 缺点:可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定。
class Singleton
{
public:
	static Singleton* GetInstance()
	{
		if (_pinst == nullptr)
		{
			_pinst = new Singleton;
		}

		return _pinst;

	}
private:

	// 构造函数私有
	Singleton() 
	{

	};

	// C++98 防拷贝
	Singleton(Singleton const&);
	Singleton& operator=(Singleton const&);

	// C++11  防拷贝
	/*Singleton(Singleton const&) = delete;
	Singleton& operator=(Singleton const&) = delete;*/

	static Singleton*  _pinst;              //静态的所有对象共享一个,而且属于这个类,类内声明,类外定义
};

Singleton*  Singleton::_pinst=nullptr;     //定义并初始化

 


int main()
{
	cout << Singleton::GetInstance() << endl;
	cout << Singleton::GetInstance() << endl;
	cout << Singleton::GetInstance() << endl;

	return 0;
}

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

5.2.2 懒汉模式

       如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取 文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化, 就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。下面这段代码实现了一个线程安全的懒汉式单例模式。懒汉式单例模式在第一次使用实例对象时才创建对象,从而避免了程序启动时的负载。为了保证线程安全和效率,使用了双重检查锁(Double-Check Locking)技术。

#include <iostream>
#include <mutex>
#include <thread>
using namespace std;

class Singleton {
public:
    // 获取单例实例的静态方法
    static Singleton* GetInstance() {
        // 第一次检查是否需要实例化单例对象
        if (nullptr == m_pInstance) {
            // 加锁保护,防止多个线程同时进入
            m_mtx.lock();
            // 第二次检查,确保单例对象只被实例化一次
            if (nullptr == m_pInstance) {
                m_pInstance = new Singleton();
            }
            // 解锁
            m_mtx.unlock();
        }
        return m_pInstance;
    }

    // 嵌套的垃圾回收类,用于在程序结束时释放单例对象
    class CGarbo {
    public:
        ~CGarbo() {
            // 删除单例对象
            if (Singleton::m_pInstance)
                delete Singleton::m_pInstance;
        }
    };
    // 静态成员变量,程序结束时会自动调用其析构函数从而释放单例对象
    static CGarbo Garbo;

private:
    // 私有构造函数,防止外部实例化
    Singleton() {}

    // 防止拷贝构造和赋值操作
    Singleton(Singleton const&);
    Singleton& operator=(Singleton const&);

    // 单例对象指针
    static Singleton* m_pInstance;
    // 互斥锁,保证线程安全
    static mutex m_mtx;
};

// 静态成员变量初始化
Singleton* Singleton::m_pInstance = nullptr;
Singleton::CGarbo Garbo;
mutex Singleton::m_mtx;

int main() {
    // 创建两个线程,同时尝试获取单例实例
    thread t1([]{ cout << Singleton::GetInstance() << endl; });
    thread t2([]{ cout << Singleton::GetInstance() << endl; });
    t1.join();
    t2.join();

    // 主线程中再次获取单例实例
    cout << Singleton::GetInstance() << endl;
    cout << Singleton::GetInstance() << endl;

    return 0;
}

5.2.3  饿汉和懒汉模式的区别

  1. 懒汉模式需要考虑线程安全和释放的问题,实现相对更复杂,饿汉模式不存在以上问题实现简单。
  2. 懒汉是一种懒加载模式需要时在初始化创建对象,不会影响程序的启动。饿汉模式则相反,程序启动阶段就创建初始化实例化对象,会导致程序启动慢,影响体验。

至此,这一讲内容介绍完毕,内容简单,星光不问赶路人,加油吧,感谢阅读,如果对此专栏感兴趣,点赞加关注!

  • 20
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

未来可期,静待花开~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值