C++11特殊类设计之单例设计模式

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

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

//1. 请设计一个类,只能在堆上创建对象
class HeapOnly
{
public:
	//不加static则无法通过类名加作用域在堆上创建对象
	static HeapOnly * CreateObj()
	{
		return new HeapOnly;
	}
private:
	HeapOnly() {};
	//防止通过拷贝在栈上创建对象
	HeapOnly(const HeapOnly & ho) = delete;
};

int main()
{
	HeapOnly * ho1 = HeapOnly::CreateObj();
	//HeapOnly ho2(*ho);//无法通过拷贝在栈上创建对象
	//HeapOnly ho3 = *ho;

	system("pause");
	return 0;
}

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

因为new在底层调用void* operator new(size_t size)函数,只需将该函数屏蔽掉即可。
注意:也要防止定位new

//2. 请设计一个类,只能在栈上创建对象
class StackOnly
{
public:
	StackOnly() {}
	//StackOnly(const StackOnly & s) = delete;不能禁止拷贝
	void * operator new(size_t size) = delete;
	void operator delete(void * p) = delete;
private:
};

int main()
{
	StackOnly s1;
	//StackOnly *s2 = new StackOnly;
	//可以通过拷贝函数在堆上创建,但如果进制拷贝的话,s1也没办法创建了,因此只能屏蔽new
	//StackOnly *s3 = new StackOnly(s1);
	system("pause");
	return 0;
}

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

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

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

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

class CopyBan
{
public:

	CopyBan(const CopyBan & cb) = delete;
	CopyBan & operator=(const CopyBan & cb) = delete;
};

在这里插入图片描述

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

C++98方式

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

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

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

请设计一个类,只能创建一个对象(单例模式)重点!!!!

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

举例使用单例模式:
我们想在快排的时候记录他递归了多少次

首先我们想一下如果想要几个关联的文件当中只能存在一个变量该怎么做?
我们先试一下全局变量,在.h文件定义一个全局变量,.cpp中包含.h文件看看这样能不能起到只有一个变量的效果
在这里插入图片描述
那我们再试一下定义一个全局的静态变量
在这里插入图片描述
有一种解决办法就是.h文件声明,使用的.cpp文件再实现,但这样写太麻烦,如果要加入其它功能还要在.h声明,.cpp定义实现,太挫了见下图!
在这里插入图片描述
我们想到,类的成员变量天生就是声明!
因此单例设计模式就是利用了这个机制实现的!

单例设计模式

如果只有一个.cpp则可以在.h定义,如果有多个.cpp则只能在.cpp定义!!!!!!

因为如果有两个.cpp文件都要用,在.h中声明加定义,会造成重定义,因为两个.cpp都有一份;
解决方式为:.h只声明,在任意一个.cpp中实例化后,整体两个.cpp的内部是共享这一个单例变量的,与前面全局静态变量原理相同。
注意:只能在一个.cpp实例化,否则多个.cpp都实例化后还是会出现重定义

单例设计模式就是为了解决只想要一个变量的场景,他一共有两种模式,分别为饿汉和懒汉模式。
饿汉:就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。
懒汉:程序启动时先不创建,等什么时候用到了在创建。

饿汉模式(饿汉不需要锁保护,因为一开始就创建好了,线程安全问题在创建的时候才能发生)

饿汉模式的单例对象是全局变量,程序一启动就建立好的
懒汉模式的单例对象是个指针,什么时候用我在什么时候初始化,就达到了懒汉的效果
.Signal.h

#pragma once
#include<iostream>
#include <vector>
#include <time.h>
#include <mutex>
#include <thread>
using namespace std;

//饿汉模式
class CallInfo
{
public:
	//每次调用的都是同一个对象
	static CallInfo& GetInstance()
	{
		return _inst;
	}

	int GetCallCount()
	{
		return _callCount;
	}

	void AddCallCount(int n)
	{
		_callCount += n;
	}

	//防拷贝
	CallInfo(CallInfo &) = delete;
	CallInfo& operator=(CallInfo &) = delete;
private:
	//只能通过匿名函数调用对象
	CallInfo()
		:_callCount(0)
	{}
private:
	int _callCount;
	static CallInfo _inst;//设置成静态是为了调用的时候都只调用这同一个变量,实现单例的功能
};

void QuickSort(int* a, int left, int right);//.cpp实现

.Signalton.cpp

#include"Signalton.h"
//饿汉模式只需要在一个.cpp定义后其他的文件都共用这一个变量!!!!!!!!!!!
CallInfo CallInfo::_inst;//类外初始化

void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; ++i)
	{
		// x[0, end]
		int end = i;
		int x = a[end + 1];
		while (end >= 0)
		{
			if (a[end] > x)
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = x;
	}
}

void QuickSort(int* a, int left, int right)
{
	CallInfo::GetInstance().AddCallCount(1);

	if (left >= right)
		return;

	if (right - left + 1 > 10)
	{
		int keyi = left;
		int prev = left;
		int cur = prev + 1;
		while (cur <= right)
		{
			if (a[cur] < a[keyi] && ++prev != cur)
			{
				swap(a[cur], a[prev]);
			}

			++cur;
		}

		swap(a[prev], a[keyi]);
		keyi = prev;

		// [left, keyi-1]keyi[keyi+1, right]
		QuickSort(a, left, keyi - 1);
		QuickSort(a, keyi + 1, right);
	}
	else
	{
		InsertSort(a + left, right - left + 1);
	}
}

test.cpp

#include"Signalton.h"


void TestOP()
{
	srand(time(0));
	const int N = 10000;
	int* a1 = (int*)malloc(sizeof(int)*N);

	for (int i = 0; i < N; ++i)
	{
		a1[i] = rand();
	}

	int begin5 = clock();
	QuickSort(a1, 0, N - 1);
	int end5 = clock();

	printf("QuickSort:%d\n", end5 - begin5);

	//cout << &callCount << endl;
	//printf("QuickSort Call Time:%d\n", callCount);
	CallInfo::GetInstance().AddCallCount(100000000);
	printf("QuickSort Call Time:%d\n", CallInfo::GetInstance().GetCallCount());

	free(a1);

}
int main()
{
	TestOP();
	return 0;
}

在这里插入图片描述

为什么懒汉模式需要加锁保护

在这里插入图片描述

懒汉模式

懒汉模式的单例对象是个指针,什么时候用我在什么时候初始化,就达到了懒汉的效果
Signalton.h

//懒汉模式
class CallInfo
{
public:

	static CallInfo & GetInstance()
	{
		//双判定保证线程安全
		if (_pInst == nullptr)
		{
			unique_lock<mutex>lock(_mtx);
			if (_pInst == nullptr)
			{
				_pInst = new CallInfo;
			}
		}
		return *_pInst;
	}

	int GetCallCount()
	{
		return _callCount;
	}

	void AddCallCount(int n)
	{
		_callCount += n;
	}
	//防拷贝
	CallInfo(CallInfo &) = delete;
	CallInfo& operator=(CallInfo &) = delete;

private:
	//只能通过匿名函数调用对象
	CallInfo()
		:_callCount(0)
	{}

private:
	int  _callCount;
	static CallInfo * _pInst;
	static mutex _mtx;       //加锁保证初始化线程安全
};


void QuickSort(int* a, int left, int right);

Signalton.cpp

#include"Signalton.h"

//懒汉模式只需要在一个.cpp定义后其他的文件都共用这一个变量
CallInfo* CallInfo::_pInst = nullptr;//类外初始化
mutex CallInfo::_mtx;


void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; ++i)
	{
		// x[0, end]
		int end = i;
		int x = a[end + 1];
		while (end >= 0)
		{
			if (a[end] > x)
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = x;
	}
}

void QuickSort(int* a, int left, int right)
{
	CallInfo::GetInstance().AddCallCount(1);//调用单例记录当前递归次数

	if (left >= right)
		return;

	if (right - left + 1 > 10)
	{
		int keyi = left;
		int prev = left;
		int cur = prev + 1;
		while (cur <= right)
		{
			if (a[cur] < a[keyi] && ++prev != cur)
			{
				swap(a[cur], a[prev]);
			}

			++cur;
		}

		swap(a[prev], a[keyi]);
		keyi = prev;

		// [left, keyi-1]keyi[keyi+1, right]
		QuickSort(a, left, keyi - 1);
		QuickSort(a, keyi + 1, right);
	}
	else
	{
		InsertSort(a + left, right - left + 1);
	}
}

test.cpp

void TestOP()
{
	srand(time(0));
	const int N = 10000;
	int* a1 = (int*)malloc(sizeof(int)*N);

	for (int i = 0; i < N; ++i)
	{
		a1[i] = rand();
	}

	int begin5 = clock();
	QuickSort(a1, 0, N - 1);
	int end5 = clock();

	printf("QuickSort:%d\n", end5 - begin5);

	//这个语句为了验证确实是共享一个单例变量,正常只有3000多
	CallInfo::GetInstance().AddCallCount(100000000);
	printf("QuickSort Call Time:%d\n", CallInfo::GetInstance().GetCallCount());

	free(a1);
}
int main()
{
	TestOP();
	return 0;
}

在这里插入图片描述

懒汉如果设计成指针可以这样调用

在这里插入图片描述

懒汉模式新写法

// 懒汉
class CallInfo
{
public:
	static CallInfo& GetInstance()
	{
		// C++98 中多线程调用时,static sInst对象构造初始化并不能保证下线程安全
		// C++11 优化了这个问题,C++11中static sInst对象构造初始化是线程安全的
		static CallInfo sInst;

		return sInst;
	}

	int GetCallCount()
	{
		return _callCount;
	}

	void AddCallCount(int n)
	{
		_callCount += n;
	}

	void Push(const pair<int, int>& kv)
	{
		_v.push_back(kv);
	}

	CallInfo(const CallInfo&) = delete;
private:
	CallInfo()
		:_callCount(0)
	{
		cout << "CallInfo()" << endl;
	}

private:
	int _callCount;
};
#include"Signalton.h"

int main()
{
	CallInfo::GetInstance().AddCallCount(10);
	cout << CallInfo::GetInstance().GetCallCount() << endl;
}

在这里插入图片描述

总结饿汉懒汉优缺点

在这里插入图片描述

懒汉模式需要释放单例对象的办法

只需要在单例的类中加入一个内嵌垃圾回收器,
在这里插入图片描述
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值