c++11 : 特殊类设计

目录

一  设计一个类:只能在堆上创建对象

二 设计一个类:只能在栈上创建对象

三 设计一个类:不能被拷贝

四  设计一个类:不能被继承 

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

六 饿汉和懒汉模式的对比


 

一  设计一个类:只能在堆上创建对象

实现方式:

  • 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象
  •  提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
class HeapOnly
{
public:
	static HeapOnly* GetObj()
	{
		return new HeapOnly;
	}
private:
	HeapOnly()
	{}
	//c++98,拷贝声明成私有
	HeapOnly(const HeapOnly&);
public:
	//c++11
	//HeapOnly(const HeapOnly&) = delete;
};

如何调用:

int main()
{
    //静态成员变量才需要在类外初始化
    //此外静态成员函数的访问不需要对象,类名+访问限定符::
	HeapOnly* p=HeapOnly::GetObj();
	//HeapOnly copy(*p);//在栈上,所以要把拷贝函数要禁掉
	std::shared_ptr<HeapOnly> sp1(HeapOnly::GetObj());
}

🍉静态变量与全局变量的区别 

静态成员变量和静态成员函数属于所有类

  1. 全局变量:全局变量的作用域具有文件作用域,即在声明它们的文件内部可见。
  2. 静态变量:全局作用域的静态变量和全局变量一样具有文件作用域,但是全局的静态变量只能作用于定于它的文件,即使其他文件包含了其所在的头文件也不能使用,而全局变量可以在其他文件使用。局部作用域的静态变量(在mian函数内部声明的静态变量)作用域在花括号括起来的区域。

二 设计一个类:只能在栈上创建对象

实现方式:

  • 将构造函数私有化,然后设计静态方法创建对象返回即可
  • 不需要将拷贝函数私有化,因为从栈上创建的对象拷贝过后也是在栈上创建对象
//2.创建出对象只能在栈上
class StackOnly
{
public:
	static StackOnly GetObj()
	{
		return StackOnly();
	}
	//第二种方法  new重载然后禁掉并且构造函数不私有  static StackOnly sso 在静态区,这是
	//这种方法缺陷之一
	//void* operator new(size_t size) = delete;
private:
	//必须这样初始化
	StackOnly()
	{}
};

三 设计一个类:不能被拷贝

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

  1. C++98
class CopyBan
 {
 // ...
 private:
 CopyBan(const CopyBan&);
 CopyBan& operator=(const CopyBan&);
 //...
 };

    2. c++11

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

四  设计一个类:不能被继承 

  • c++98

class NonInherit
 {
 private:
    NonInherit()
    {}
 };

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

  • c++11:final关键字,final修饰类,表示该类不能被继承
class A  final
 {
    // ....
 };

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

单例模式:一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全 局访问点,该实例被所有程序模块共享。使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性.

class Singleton
{
public:
	//如果这个函数要不是静态的,就需要对象调用,我们以及
	//把构造函数给禁掉了
	static Singleton* GetInstance()
	{
		if (_pinst == nullptr)
		{
			_pinst = new Singleton;
		}
		return _pinst;
	}
private:
	//构造函数私有化,这样就不能随意创建对象
	Singleton()
	{}
	//拷贝构造禁掉
	Singleton(const Singleton& s) = delete;
	static Singleton* _pinst;
};
//静态成员变量类似全局变量要在类外初始化
Singleton* Singleton::_pinst = nullptr;
int main()
{
	//拷贝构造又构造了一个新的对象,所以单例模式要把拷贝禁掉
	//Singleton copy(*Singleton::GetInstance());
}

🍎但是上述代码存在着线程安全问题,即线程都走到 _pinst=new Singleton 处其中一个线程被cpu切走,一个线程创建类后返回之后,cpu又把之前切走的线程切换回来,并加载上下文,此时线程会继续 new Singleton,这样就创建了两个对象,比符合单例模式,因为我们需要进行加锁。

class Singleton
{
public:
	//如果这个函数要不是静态的,就需要对象调用,我们以及
	//把构造函数给禁掉了
	static Singleton* GetInstance()
	{
		_mtx.lock();
		if (_pinst == nullptr)
		{
			_pinst = new Singleton;
		}
		_mtx.unlock();
		return _pinst;
	}
	//拷贝构造禁掉
	Singleton(const Singleton& s) = delete;
private:
	//构造函数私有化,这样就不能随意创建对象
	Singleton()
	{}

	static Singleton* _pinst;
	static mutex _mtx;//静态成员函数只能访问静态成员变量因为没有this指针,所以mutex必须静态
};
//静态成员变量类似全局变量要在类外初始化
Singleton* Singleton::_pinst = nullptr;
mutex Singleton::_mtx;

再次优化:

static Singleton* GetInstance()
	{
		if (_pinst == nullptr)
		{
			_mtx.lock();
			//shared_ptr<mutex> lock(&_mtx);,出了if作用域会调用析构函数进行释放资源
			if (_pinst == nullptr)
			{
				//加锁保护的是_pinst=nullptr这段,当创建成功对象的时候,线程不会进入
				//if(_pinst==nullptr)这段代码,而我们每次上锁解锁造成了资源浪费
				//所在我们在外面加入了又加入if语句
				_pinst = new Singleton;
			}
			_mtx.unlock();
		}
		
		return _pinst;
	}

为了避免造成内存泄漏,我们写一个手动释放资源的函数 

	static void DelInstance()
	{
		unique_lock<mutex> lock(_mtx);
		delete _pinst;
		_pinst = nullptr;
	}

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

  • 饿汉模式: 一开始(main函数之前)就创建对象
  • 懒汉模式:第一次获取对象时,再创建对象

🎂上述代码我们是实现了懒汉模式的单例模式,为什么说上述实现的单例模式是懒汉模式呢?

我们从创建对象代码进行分析

	static Singleton* GetInstance()
	{
		if (_pinst == nullptr)
		{
			_mtx.lock();
			if (_pinst == nullptr)
			{
				_pinst = new Singleton;
			}
			_mtx.unlock();
		}
		
		return _pinst;
	}

当我们在main()内调用GetInstance()获取对象的时候,刚开始_pinst==nullptr,所以会在我们获取对象的时候创建。而饿汉模式一开始(main函数之前)就创建对象,接下里我们分析一下饿汉模式

饿汉模式 

namespace hungry_man
{
	//1.懒汉模式  一开始(main函数之前)就创建对象
	class Singleton
	{
	public:
		static Singleton* GetInstance()
		{
			return &_inst;
		}
		Singleton(const Singleton& s) = delete;
	private:
		static Singleton _inst;//饿汉模式静态成员变量不是指针,可以看出是类
		//静态成员变量可以看出全局变量,在main之前就以及创建好类了,所以不存在线程安全问题
		Singleton()
		{}
	};

	Singleton Singleton::_inst;

	void x()
	{
		cout << Singleton::GetInstance() << endl;
		cout << Singleton::GetInstance() << endl;
		cout << Singleton::GetInstance() << endl;

	}
}
int main()
{
	hungry_man::x();
}

🎉:饿汉模式与懒汉模式结构上不同在一,饿汉模式的私有成员变量是 Singleton Singleton::_inst,不是指针,饿汉模式在类外对 _inst进行初始化的时候,就相当于已经在main()之前创建好对象了,当我们在main()调用x()函数的时候

即cout << Singleton::GetInstance() << endl;所获取的对象是早就在main函数之前已经创建好的,且由于在主线程之前就已经创建好,所以不存在线程安全问题,不需要加锁。

从运行结果来看我们可以得到即使我们调用了三次获取对象函数,得到的都是一个类对象。 


六 饿汉和懒汉模式的对比

  1. 懒汉模式需要考虑线程安全问题和释放问题,实现相对于来说较为复杂,饿汉模式不存在以上问题实现简单。
  2. 懒汉模式是一种懒加载模式需要的时候再初始化创建对象,不会影响程序的启动,饿汉模式则相反,程序启动阶段就创建初始化对象,会导致程序启动慢。
  3. 如果有多个单例类,需要B依赖A,要求A单例创建初始化之后,B才能创建初始化,那么B就不能是饿汉模式,无法保证顺序。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值