C++特殊类设计【特殊类 || 单例对象 || 饿汉模式 || 懒汉模式】

 博客主页:花果山~程序猿-CSDN博客

文章分栏:C++——从入门到入土,安排!_花果山~程序猿的博客-CSDN博客

关注我一起学习,一起进步,一起探索编程的无限可能吧!让我们一起努力,一起成长!

目录

一,特殊类设计 

1. 只在堆上创建的类

2. 只允许在栈上创建的类 

3. 不能被继承的类

4. 不能被拷贝的类

 5. 设计一个类,只能创建一个对象(单例对象)

饿汉模式

懒汉模式

C++11静态成员初始化多线程安全问题

二, C++类型转换

C语言类型转换存在的问题

1. static_cast(近似类型)

2. reinterpret_cast(不相关类型)

3. const_cast 

4. dynamic_cast(一般用于多态)

5. RTTI(了解)

结语


嗨!收到一张超美的风景图,愿你每天都能顺心!

一,特殊类设计 

1. 只在堆上创建的类

实现方式:
1. 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
// 方法一
class HeapOnly
{
public:
	void Destory()
	{
		cout << "~HeapOnly" << endl;
		delete this;
	}

private:
	~HeapOnly()
	{}

	int i;
};
// 将析构函数设为私有,这样在栈中的对象(不需要手动析构),在栈结束时
// 无法自动调用析构函数。

// 方法二:构造函数私有,仅提供一个creat函数来进行构造
class HeapOnly_2
{
public:
	// static 添加原因:我们知道调用成员函数需要对象,但我们这时并没有对象,
	// 因此需要用static修饰。(先有鸡先有蛋问题)
	static HeapOnly_2*  creat_(int b) 
	{
		return new HeapOnly_2(b);
	}
	// 防止默认拷贝,赋值构造生成,在栈上创建
	HeapOnly_2(const HeapOnly_2& b) = delete;
	HeapOnly_2& operator=(const HeapOnly_2& b) = delete;
private:
	HeapOnly_2(int b)
		:i(b)
	{}

	int i;
};

int main()
{
	// HeapOnly it;
	HeapOnly* it = new HeapOnly;
	it->Destory();

	//HeapOnly_2 it2;
	HeapOnly_2* it2 = HeapOnly_2::creat_(1);
	//HeapOnly_2 it3 = *it2; 
	//HeapOnly_2 it3(*it2);
    delete it2
	return 0;
}

2. 只允许在栈上创建的类 

class StackOnly
{
public:
	static StackOnly creat_(int b)
	{
		return StackOnly(b); //意味着拷贝构造不能禁止 
	}

	// creat_返回值是右值,我们拷贝构造参数只设置右值,同时默认构造也不会产生
	StackOnly(StackOnly&& st)
		:_i(st._i)
	{}
	//但这也没有禁止,move一下就又可以了

private:
	// 构造一私有,堆栈上都无法构造创建
	StackOnly(int i)
		:_i(i){}

	StackOnly& operator=(const StackOnly& st) = delete;

	int _i;
};

方法同1类似,但这无法限制住,static对象的拷贝。

3. 不能被继承的类

C++98方式,将构造函数私有,派生类无法调用到构造函数导致无法被继承。

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

C++11新方式,在目标类添加关键词——final,表示该类无法被继承

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

4. 不能被拷贝的类

拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此 想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可
C++98
将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可
class CopyBan
{
    // ...
    
private:
    CopyBan(const CopyBan&);
    CopyBan& operator=(const CopyBan&);
    //...
};
原因:
1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了
2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。

C++11
C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。
class CopyBan
{
    // ...
    CopyBan(const CopyBan&)=delete;
    CopyBan& operator=(const CopyBan&)=delete;
    //...
};

 5. 设计一个类,只能创建一个对象(单例对象)

设计模式:
设计模式(Design Pattern)是一套 被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。我们学过的有适配器,迭代器,包装器,以及扩展:工厂,观察者模式。下面的单例模式也是
单例模式:
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。单例模式有两种实现模式:

饿汉模式

就是说不管你将来用不用,程序启动时就先创建一个唯一的实例对象
class SingleCase
{
public:
	static SingleCase* GetInstace()
	{
		return _sc;
	}
	
	size_t GetSize()
	{
		return _vc.size();
	}

	void Add(const string& str)
	{
		_mtx.lock();
		_vc.push_back(str);
		_mtx.unlock();
	}
	
	void print()
	{
		for (auto& it : _vc)
		{
			cout << it << endl;
		}
	}

	SingleCase(const SingleCase& it) = delete;
	SingleCase& operator=(const SingleCase& it) = delete;
private:
	SingleCase()
	{}

	mutex _mtx;
	vector<string> _vc;
	static SingleCase* _sc;
};
// 声明定义分离,_sc是也在类成员中(能调用类成员),虽然看起来像在类外。
SingleCase* SingleCase::_sc = new SingleCase;

 饿汉的缺点:

1. 单例对象的构造可能会占用大量资源,对应用程序的打开比较慢。

2. 不适合多个单例对象之间存在依赖关系生成。如单例A,单例B,必须得先有A再有B,如果在同一个源文件中跟声明顺序相同;但如果不在同一个源文件中,产生顺序就不确定。

优点:代码简单,支持一些高并发场景

懒汉模式

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

class SingleCase_lazy
{
public:
	static SingleCase_lazy* GetInstace()
	{
		// 处理大多数情况,提高效率
		if (_sc == nullptr)
		{
			SingleCase_lazy::new_mtx.lock();
			// 两程序几乎同一时间进入该
			// 作用域时,再进行判断
			if (_sc == nullptr) 
			{
				// 加锁防止_sc发生覆盖
				_sc = new SingleCase_lazy;
			}
			SingleCase_lazy::new_mtx.unlock();
		}
		return _sc;
	}

	

	size_t GetSize()
	{
		return _vc.size();
	}

	void Add(const string& str)
	{
		_vmtx.lock();
		_vc.push_back(str);
		_vmtx.unlock();
	}

	void print()
	{
		for (auto& it : _vc)
		{
			cout << it << endl;
		}
	}

	 static void Destory()
	{
		new_mtx.lock();
		if (_sc)
		{
			cout << "delete _sc" << endl;
		}
		new_mtx.unlock();

		
		delete _sc;
	}

	class De
	{
	public:
		~De()
		{
			Destory();
		}
	};

	SingleCase_lazy(const SingleCase_lazy& it) = delete;
	SingleCase_lazy& operator=(const SingleCase& it) = delete;
private:
	SingleCase_lazy()
	{}
	static mutex new_mtx;
	mutex _vmtx;
	vector<string> _vc;
	static SingleCase_lazy* _sc; //无法显示调用析构,我们通过内部类,调用
};
SingleCase_lazy* SingleCase_lazy::_sc = nullptr;
mutex SingleCase_lazy::new_mtx; // 静态函数的锁
SingleCase_lazy::De _de;

总结:设计一个单例对象,我们需要满足:1. 把数据放一个类中,把这个类设计成单例类。 2. 保持进程唯一,需要将拷贝,赋值都禁止。 3. 设计模式是饿汉,还是懒汉

C++11静态成员初始化多线程安全问题

class SingleInstance {
public:
	static SingleInstance* GetInstance() {
		// 局部静态变量实现的懒汉式单例,C++11后线程安全
		// 在C++11之前,不能保证多线程安全
		static SingleInstance instance;
		return &instance;
	}
private:
	SingleInstance();
	~SingleInstance();
	SingleInstance(const SingleInstance& signal) = delete;
	SingleInstance& operator=(const SingleInstance& signal) = delete;
};

二, C++类型转换

标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:
static_cast、reinterpret_cast、const_cast、dynamic_cast

C语言类型转换存在的问题

C风格的转换格式很简单,但是有不少缺点的:
1. 隐式类型转化有些情况下可能会出问题:比如数据精度丢失
2. 显式类型转换将所有情况混合在一起,代码不够清晰
因此C++提出了自己的类型转化风格,注意 因为C++要兼容C语言,所以C++中还可以使用C语言的转化风格

1. static_cast(近似类型)

static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast,但它不能用于两个不相关的类型进行转换。
int main()
{
  double d = 12.34;
  int a = static_cast<int>(d);
  cout<<a<<endl;
  return 0;
}

2. reinterpret_cast(不相关类型)

reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为 另一种不同的类型
int main()
{
 double d = 12.34;
 int a = static_cast<int>(d);
 cout << a << endl;
 // 这里使用static_cast会报错,应该使用reinterpret_cast
 //int *p = static_cast<int*>(a);
 int *p = reinterpret_cast<int*>(a);
 return 0;
}

3. const_cast 

const_cast 最常用的用途就是删除变量的 const 属性,方便赋值
int main()
{
	volatile const int i = 10;
	int* it = const_cast<int*>(&i);
	*it = 2;
	
	cout << i << endl;  // 10
	cout << *it << endl; // 2
	// i为什么没有被修改?答:i被编译器优化,i被10替换到寄存中,并没有在内存中获取
	//在C++中,volatile关键字用于告诉编译器该变量的值可能会在程序的控制之外被改变,
	//因此编译器不应该对该变量进行优化。这意味着每次访问该变量时,编译器都会从内存
    // 中读取最新的值,而不是使用之前缓存的值 (来自gpt的回答)
	return 0;
}

4. dynamic_cast(一般用于多态)

dynamic_cast 用于将一个父类对象的指针 / 引用转换为子类对象的指针或引用 ( 动态转换 )

 

注意:
1. dynamic_cast 只能用于父类含有 虚函数 的类。

 2. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0。

class A
{
public :
virtual void f(){}
};
class B : public A
{};
void fun (A* pa)
{
// dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回
B* pb1 = static_cast<B*>(pa);
B* pb2 = dynamic_cast<B*>(pa);
cout<<"pb1:" <<pb1<< endl;
cout<<"pb2:" <<pb2<< endl;
}
int main ()
{
  A a;
  B b;
  fun(&a);
  fun(&b);
  return 0;
}

5. RTTI(了解)

RTTI Run-time Type identification 的简称,即: 运行时类型识别
C++ 通过以下方式来支持 RTTI
1. typeid 运算符(获取类型)
2. dynamic_cast 运算符
3. decltype

结语

   本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力

  • 25
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值