【C++】特殊类设计+单例模式+类型转换


目录

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

1、C++98

2、C++11

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

1、将构造设为私有

2、将析构设为私有

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

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

1、C++98

2、C++11

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

1、饿汉模式设计单例模式

2、懒汉模式设计单例模式

3、单例对象的释放

4、一种比较简洁但是可能存在线程安全的单例懒汉模式

六、类型转换

1、C语言类型转换

2、C++新增四种强制类型转换

2.1static_cast

2.2reinterpret_cast

2.3const_cast

2.4dynamic_cast

3、RTTI


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

1、C++98

class CopyBan
{
private:
	CopyBan(const CopyBan& cb);
	CopyBan& operator=(const CopyBan& cb);
};
int main()
{

	return 0;
}

        1、将拷贝构造和赋值运算符重载设置为私有;

        2、仅仅私有还不够,这并不能防止类内部就行拷贝。还要对拷贝构造和赋值运算符重载只声明却不实现。

2、C++11

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

        C++11直接使用delete禁用拷贝构造和赋值运算符重载。

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

1、将构造设为私有

        1、将构造设为私有,防止外部构造,并提供一个CreateObj的函数用于构造堆区对象;

        2、但是外部需要对象来调用CreateObj函数来构造对象,所以需要将CreateObj设置为静态函数,无需对象也能调用。

        3、外部通过CreateObj函数构造一个对象后,外部可以利用这个对象的指针拷贝构造一个栈区的对象,需要禁用拷贝构造。

2、将析构设为私有

        将析构函数设置为私有,栈区对象由于无法析构所以无法创建。堆区对象需要手动调用自己写的清理函数释放。

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

        1、将构造函数私有;

        2、提供一个静态的CreateObj方法用于构造栈区对象;

        3、但是无法防止外部构造静态对象。

        4、如果想彻底禁止生成静态的对象,需要再禁用拷贝构造。不过这样这个类只能生成临时对象或者引用的对象了,不能修改。

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

1、C++98

class FinalClass
{
	static FinalClass CreateObj()
	{
		return FinalClass();
	}
private:
	FinalClass()
	{}
};

        1、禁用构造函数;

        2、提供一个静态的CreateObj函数供外部创建父类对象,但是子类会因为构造私有的继承不可见的原因无法构造出父类对象,所以无法继承。

2、C++11

class FinalClass final
{
private:
	
};

        C++11直接使用final关键字

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

        一个类只能创建一个对象,即单例模式。该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。

1、饿汉模式设计单例模式

class InfoSingleton
{
public:
	static InfoSingleton& GetInstance()
	{
		return _sins;
	}
	void Insert(string name, int money)
	{
		_info[name] = money;
	}
	void Print()
	{
		for (auto kv : _info)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
	}
private:
	InfoSingleton()
	{}
	InfoSingleton(const InfoSingleton&) = delete;
	InfoSingleton& operator=(const InfoSingleton&) = delete;
	map<string, int> _info;
	static InfoSingleton _sins;
};
InfoSingleton InfoSingleton::_sins;
int main()
{
	InfoSingleton& s = InfoSingleton::GetInstance();//使用单例对象
	s.Insert("小明", 100);
	s.Insert("小红", 50);
	s.Insert("小绿", 130);
	s.Print();
	cout << endl;
	s.Insert("小绿", 10086);
	s.Print();
	return 0;
}

        饿汉模式:在main函数被加载之前就创建好对象。(全局和静态将在main函数之前被加载)

        1、私有构造函数,禁用拷贝构造和赋值运算符重载;

        2、在类中声明、外部定义一个静态的对象,用于调用类中私有的构造函数,同时作为单例对象被使用;

        3、在类中提供一个获取静态对象的函数GetInstance,为了外部可调用,所以将该函数设置为静态。

饿汉模式的特点:

1、单例对象初始化时,数据太多会导致启动慢;

2、如果多个单例类有初始化的依赖关系,饿汉模式无法控制。例如A和B都是单例类,因为B的启动依赖A,所以需要先初始化A,再初始化B,但是饿汉模式无法控制对象的初始化顺序。

3、饿汉模式创建的对象绝对不会有线程安全问题,因为该模式的对象在main函数之前已经被创建好了,mian函数之前线程都没启动呢。

2、懒汉模式设计单例模式

        饿汉模式:第一次获取单例对象的时候创建对象;

        1、私有构造函数,禁用拷贝构造和赋值运算符重载;

        2、在类中声明、外部定义一个静态的对象指针

        3、在类中提供一个获取静态对象的函数GetInstance,为了外部可调用,所以将该函数设置为静态。

        4、它与饿汉的写法区别如图红色标记处。

懒汉模式的特点:

1、对象在main函数之后才会创建;

2、可以主动控制对象的创建时机。

3、创建对象时存在线程安全问题,如果多个线程同时进入红框区域,会可能new多个对象,最后一个创建的对象指针会覆盖之前创建的对象指针,导致内存泄露。

        懒汉模式需要加锁解决线程安全问题:

        不过红框中new也可能会失败,再套一层try/catch看着太累,可以使用RAII锁解决:

        new这里还要try一下异常,main里函数捕获这个异常。(写漏了)

3、单例对象的释放

        1、一般单例对象不需要考虑释放,资源会在进程结束时自动释放;

        2、释放的写法如下:可以手动清理,将一些资源保存:可手动调用DelInstance进行资源的回收,main函数结束时,操作系统也会自动回收单例对象的资源。

4、一种比较简洁但是可能存在线程安全的单例懒汉模式

class InfoSingleton
{
public:
	static InfoSingleton& GetInstance()
	{
		//静态局部变量是在main函数之后才创建初始化
		static InfoSingleton sinst;
		return sinst;
	}
	void Insert(string name, int money)
	{
		_info[name] = money;
	}
	void Print()
	{
		for (auto kv : _info)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
	}
private:
	InfoSingleton()
	{}
	InfoSingleton(const InfoSingleton&) = delete;
	InfoSingleton& operator=(const InfoSingleton&) = delete;
	map<string, int> _info;
};

        1、私有构造函数,禁用拷贝构造和赋值运算符重载;

        2、通过GetInstance返回静态对象(静态局部变量是在main函数之后才创建初始化)

这种方式构建的单例懒汉模式在C++11发布之前会有线程安全问题,多线程环境下可能会造成静态对象被初始化多次;而C++11规定静态局部变量是线程安全的,可以放心使用。 

六、类型转换

1、C语言类型转换

        1、隐式类型转换2、强制类型转换。

2、C++新增四种强制类型转换

        C++尤其认为隐式类型转化有些情况下可能会出问题:比如数据精度丢失。

2.1static_cast

int main()
{
	double d = 12.34;
	int a = static_cast<int>(d);
	cout << a << endl;
	return 0;
}

static_cast适用于相似类型的转换。(可以隐式类型转换的都能用static_cast) 

2.2reinterpret_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;
}

reinterpret_cast适用于不想关类型之间的转换。(不能隐式类型转换,只能强制类型转换的用reinterpret_cast)

2.3const_cast

const_cast用于删除变量的const属性。需要关注内存可见性问题。 

        编译器对const变量会有优化,认为const变量不会被改变,编译器在优化代码时可能会将变量放到寄存器或者其他高速缓存中。可以在a初始化时加上volatile关键字,加了volatile关键字后,对变量的读取和写入操作会从内存中进行,而不是从缓存中进行。

2.4dynamic_cast

        dynamic_cast用于父类的指针和引用转换为子类对象的指针和引用。(动态转换)

        向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)

        向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)

class A
{
public:
	virtual void f() {}
	int _a = 0;
};
class B : public A
{
public:
	int _b = 0;
};
void fun(A* ptr)
{
	//B* bptr = (B*)ptr;//直接转换是不安全的,父给子存在非法访问隐患
	B* bptr = dynamic_cast<B*>(ptr);
	if (bptr)//如果转换成功
	{
		bptr->_a++;
		bptr->_b++;
		cout << bptr->_a;
		cout << bptr->_b;
	}
}
int main()
{
	A aa;
	B bb;
	fun(&aa);//转换失败
	fun(&bb);//转换成功
	return 0;
}

注意:

1. dynamic_cast只能用于父类含有虚函数的类

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

3、RTTI

        RTTI:Run-time Type identifification的简称,即:运行时类型识别。

        C++通过以下方式来支持RTTI:

1. typeid运算符 
2. dynamic_cast运算符 
3. decltype

猜你喜欢

转载自blog.csdn.net/gfdxx/article/details/130436928
今日推荐