C++中的特殊类设计方法与类型转换

一、特殊类设计方法

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

例如ostream等类,因为缓冲区等缘故进行拷贝消耗极大

做法:

C++98:将拷贝构造和赋值重载设置为private,声明但不实现

C++11:直接把拷贝构造和赋值重载=delete

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

1.2.1在静态区/栈区/堆区上创建对象

静态区:

static Date d1;

栈区:

Date d2;

堆区:

Date* pd3=new Date;

1.2.2创建原理

主要的思路是封锁创建的各种方式,只提供一个接口来进行创建对象

实现起来就是:将构造函数私有化,在接口中实现在堆上创建对象,并将拷贝构造和赋值重载进行delete

注意:接口必须为静态成员函数,否则可能会出现没有对象无法调用成员函数的问题

 代码实现:

class OnlyHeap
{
public:
	static OnlyHeap* CreatObj()
	{
		return new OnlyHeap;
	}

	OnlyHeap(const OnlyHeap& oh) = delete;
	OnlyHeap& operator=(const OnlyHeap& oh) = delete;

private:
	OnlyHeap() {};
};

int main()
{
	OnlyHeap oh1;//无法运行
	OnlyHeap* oh2=OnlyHeap::CreatObj();//这是创建对象的唯一方式,且只能够创建堆上的对象
}

1.2.3第二种创建方式

 如果说第一种思路是“封前路”,那么思路二就是“堵后路”

把析构私有化,这样以后不能直接在栈上创建对象,因为它要自动调用析构,但此时无法析构

此时直接在堆上new是被允许的(因为new出来的可能是内置类型或者自定义类型,而只有自定义类型需要显式调用析构),

之后我们只需要显式去调用一下DeleteObj来清理资源

class OnlyHeap
{
public:
	void DeleteObj()
	{
		delete this;
	}

private:
	~OnlyHeap() {};
};

int main()
{
	//OnlyHeap oh1;//此时不可以直接创建
	OnlyHeap* oh2=new OnlyHeap;
	OnlyHeap* oh3=oh2;//创建的oh3也是指向堆上空间

}

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

整体设计思路与堆相似,只需要在CreatObj中return OnlyStack()即可

但要注意:拷贝构造不可以设置为delete,因为OnlyStack()需要调用,因此可能会出现这样的问题:

//假设A为结构体对象
A a1 = A::CreatObj();
A* a2=new A(a1);

对于这个问题可以考虑把operator new置为delete 

void* operator new(size_t size) = delete;

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

C++98:私有化构造

C++11:class A final{};//使用final关键字

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

1.5.1设计模式是什么

是一套被反复使用,多数人知晓,经过分类的,代码设计经验的总结

使用的目的是让代码更容易被他人理解,例如此时的单例模式,实现stl容器用到的迭代器模式等等

1.5.2单例模式实现方法一:饿汉模式

所谓单例模式,就是说一个类只能创建一个对象,该模式可保证系统中该类只有一个实例,被所有程序模块共享

所谓饿汉模式,是一种单例模式的实现思路,即“提前创建好对象,避免饿到

具体实现思路是

①将构造函数私有化,提供一个create接口,需要用到这一对象时可以通过接口直接获取

②设置一个静态成员变量为本身;再去类外声明一次,保证在编译时创建

③拷贝构造和赋值重载有破坏单例模式的风险,需要用delete封一下

代码示例:

class InfoMgr
	{
	public:
		static InfoMgr& GetInstance()
		{
			return _ins;
		}
	
		void Print()
		{
			cout << _ip << endl;
			cout << _port << endl;
			cout << _buffSize << endl;
		}
	private:
		InfoMgr(const InfoMgr&) = delete;
		InfoMgr& operator=(const InfoMgr&) = delete;
	
		InfoMgr()
		{}
	private:
		string _ip = "127.0.0.1";
		int _port = 80;
		size_t _buffSize = 100;
		
	
		static InfoMgr _ins;
	};
	
	InfoMgr InfoMgr::_ins;

    int main()
    {
        InfoMgr info1=GetInstance();
        InfoMgr info2=GetInstance();
        return 0;
    }

饿汉模式有一些缺陷

 ①如果有多个饿汉模式的单例,初始化的内容会比较多,启动缓慢

②A和B两个饿汉,对象初始化的时候有依赖关系,要求先A后B,饿汉无法保证

1.5.3单例模式实现方法二:懒汉模式

所谓懒汉模式,是一种单例模式的实现思路,即“在第一次调用的时候才创建对象,绝不勤快

实现思路是将自身的静态成员变量设置为指针,在类外初始化给nullptr

在GetInstance()函数中判断一些是否为nullptr,如果是再申请空间

但是这样实现是有线程安全的风险的,为此C++11特地支持了局部静态成员变量的申请,以此应对这一风险。

示例代码:

class InfoMgr
{
public:
	static InfoMgr& GetInstance()
	{
		static InfoMgr ins;
		return ins;
	}

	void Print()
	{
		cout << _ip << endl;
		cout << _port << endl;
		cout << _buffSize << endl;
	}
private:
	InfoMgr(const InfoMgr&) = delete;
	InfoMgr& operator=(const InfoMgr&) = delete;

	InfoMgr()
	{}
private:
	string _ip = "127.0.0.1";
	int _port = 80;
	size_t _buffSize = 1;
	
};

二、C++的类型转换

2.1内置类型之间相助转换

隐式类型转换:整形家族/整形与浮点数之间

显式(强制)类型转换:如指针和整形,相互间有一定关联关系的类型

2.2内置类型与自定义类型之间转换

2.2.1内置类型->自定义类型

通过构造函数支持,如单参数构造函数隐式类型转换,多参数构造函数隐式类型转换

用起来可以是A a1 = 1;这样即可构造

2.2.21补:explicit修饰构造函数

正常情况下只要支持了单参数构造,就可以使用单参数构造函数隐式类型转换

但如果使用explicit修饰了构造函数

如 explicit A(int a)

就不支持隐式类型转换了,但依旧可以通过强势类型转换实现

2.2.2自定义类型->内置类型

通过运算符重载支持,(原本C语言强制类型转换用的是(),但C++中()的重载逻辑已经给了仿函数,所以推出了一种特殊的重载方式)

如A类型->int类型

class A
{
//...
operator int()
{
    return _a1;
}
//...
};

//在main函数调用
int main()
{
    A aa1(5);
    int a=aa1;//就是int a=aa1.operator int()
    return 0;
}
    

2.3自定义类型之间相互转换

通过构造函数重载来支持,如在结构体B中实现一个A的重载

B(const A& aa)
:_b(aa._a)
{}

2.4特殊情况:const迭代器接收普通迭代器对象完成遍历

把普通对象传给const对象时,发生权限的缩小问题以及权限的放大都是只针对于const修饰的指针和引用类型对象,迭代器类型并不属于

因为

所以它是两个单独的类,属于类型转换

因此直接把普通迭代器给const迭代器是不可行的

需要考虑用构造函数来支持自定义->自定义

可以直接参考库中的做法:

①传入普通迭代器类型时,属于拷贝构造

②传入const迭代器类型,属于普通构造

二者共同点是都创建了const迭代器对象 

2.5C++中对于类型转换的规范

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

static_cast,reinterpret_cast,const_cast,dynamic_cast

2.5.1static_cast

对应隐式类型转换,数据本身意义不变

如将浮点数丢掉一些精度转换为整形

double d=3.14;
int a=static_cast<int>(d);

2.5.2reinterpret_cast

对应强制类型转换,数据意义改变

如将int*类型转换为int类型

int* a1=new int;
int a2=reinterpret_cast<int>(a1);

2.5.3const_cast

对应强制类型转换中有风险地去掉const属性

const int a=10;
int* pa=const_cast<int*>(&a);
*pa=100;

 注意:此时如果打印会出现问题

为什么a没有被修改呢?

原因是编译器对const修饰地内容进行了“优化”

C++中const修饰的部分实际上是常变量, 所以去掉const属性后他们还可以被修改,但这不同于C语言中的常量,为此编译器将常变量进行了部分优化,使得在进行访问操作时,const地内容只遵循第一次设置的内容

可以使用”volatile“关键字来取消优化

2.5.4dynamic_cast

2.5.4.1情景设置 

这是一个应用于继承体系下的类型转换,用来避免父类对象接收子类对象后造成的安全问题

假设此时有这样一个基类和派生类:

①派生类中有自己的成员变量

②基类中有一个虚函数

struct AA
{
public:
	virtual void func(){}

	int _a = 0;
};

struct BB:public AA
{
	int _b = 1;
};

他们在一个函数中被调用,

①此函数传入的是基类对象

②函数中需要把基类对象进行类型转换为派生类对象 

 ③函数中对基类和派生类对象进行访问

void func1(AA* aa)
{
	BB* bb = (BB*)aa;
	cout << bb->_a << endl;
	cout << bb->_b << endl;

}

 那么我们在调用函数的时候,都可能出现哪些情况呢?

①传入的是基类对象的指针

②传入的是派生类对象的指针

AA a;
BB b;
func1(&a);//传入基类对象的指针
func1(&b);//传入派生类类对象的指针

此时打印会发现

2.5.4.2问题提出 

这很明显出现了问题

我们传入的是基类指针,却在访问派生类对象的专属成员变量

 这是什么缘故呢?

我们已知

向上转型:子类对象/引用->父类对象/引用不需要转换,赋值是兼容的

向下转型:父类对象/引用->子类对象/引用有风险

在func1()中,

B* pb1=(B*)pa;就是父类转子类

对它来说,用static_cast和reinterpret_cast都是不安全的

传入父类指针,但是被转换成子类并访问子类专属元素_b是不会被禁止的

访问会读取出随机数,只有写会报错

2.5.4.3问题解决

所以func1中的强制类型转换风险极大,可以改为更好的 dynamic_cast

 此时如果我们选择使用dynamic_cast,会自动检测,能成功则转换 (原始指向子类对象的时候可以成功),不能成功则返回NULL (原始指向父类对象的时候不能成功)

BB* bb = dynamic_cast<BB*>(aa);

 

2.5.4补:为什么基类要有虚函数

 因为使用dynamic_cast需要多态(父类中需要有虚函数)

dynamic_cast的本质是去虚表中通过标识来确定指针原始指向的

2.6RTTI运行机制

是Run-timeType identification 即运行时类型识别,可以称其为一种编程语言特性

它在C++中的体现如typeid运算符,dynamic_cast运算符,decltype类型识别

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值