C++ 特殊类设计 类型转换

1.不能被拷贝的类

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

C++98做法

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

拷贝 赋值只声明不定义,放在私有 防止有人在类外定义使用。

C++11做法

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

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

2.只能在堆上创建对象的类

对象可以在哪里创建?

class A
{};

int main()
{
	static  A a1; //静态区
	 A a2; //栈
	 A* p = new A;//堆
}

方法一:

只允许new出来的对象,其它生成对象的方式全部禁止。

我们把对象的构造函数私有化,在类外就不能生成对象了

再在类中提供初始化对象的函数供类外调用生成对象,因为只能通过函数调用的方式生成对象,所以该函数必须是静态函数。

但是在类外也可以通过拷贝构造/赋值的方式来初始化,所以我们也要把拷贝/赋值删除。

class HeapOnly
{
public:
	static HeapOnly* CreateObject()
	{
		return new HeapOnly;
	}
private:
	HeapOnly() {}

	HeapOnly(const HeapOnly&) = delete;
	HeapOnly& operator=(const HeapOnly&) = delete;
};
int main()
{
	HeapOnly* p=HeapOnly::CreateObject();
	//HeapOnly a(*p);
}

方法二:

除了把对象的构造函数私有化,还可以把析构私有化。

析构私有化后在栈 静态区上都不能创建对象,因为不能调用析构。但可以new对象,因为new不会显示调用析构。

再通过一个函数来代替delete,因为已经创建出了对象可以不写成静态函数。

(在类外进行拷贝初始化也是调用不到析构,不用删除也可以)

class HeapOnly
{
public:
	HeapOnly() {};
	void Delete()
	{
		delete this;
	}
private:
	~HeapOnly() {}
};
int main()
{
	//HeapOnly p;
	HeapOnly* p = new HeapOnly;
	p->Delete();
}

3.只能在栈上创建对象的类

我们可以模仿在堆上创建对象的方法一

class HeapOnly
{
public:
	static HeapOnly CreateObject()
	{
		return HeapOnly();
	}
private:
	HeapOnly() {};

	HeapOnly(const HeapOnly&) = delete;
	HeapOnly& operator=(const HeapOnly&) = delete;
};
int main()
{
	HeapOnly* p=HeapOnly::CreateObject();
	//HeapOnly a(*p);
}

出错的原因是因为是在栈上创建的对象,返回对象是通过值返回的。必须要调用拷贝构造函数。

不封死拷贝构造函数,还有什么办法呢?

我们知道如果在类中重载new的实现,通过new创建对象时会调用重载的new,把重载new封死,就算通过拷贝构造初始化也不能创建对象。

class StackOnly
{
public:
	static StackOnly CreateObj()
	{
		return StackOnly();
	}

	void* operator new(size_t size) = delete;
	void operator delete(void* p) = delete;
private:
	StackOnly()
		:_a(0)
	{}
private:
	int _a;
};
int main()
{
	StackOnly p = StackOnly:: CreateObj();
	StackOnly a = new StackOnly(p);
	static StackOnly b = StackOnly(p);
}

但在静态区还是可以通过拷贝构造来初始化对象。

没有办法不通过拷贝构造传对象的方法吗?
有,可以通过移动构造,但在静态区创建对象时也可以通过move走移动构造初始化对象。

class StackOnly
{
public:
	static StackOnly CreateObj()
	{
		return StackOnly();
	}
	StackOnly(StackOnly&& s)
			{}
	void* operator new(size_t size) = delete;
	void operator delete(void* p) = delete;
	StackOnly(const StackOnly& s) = delete;
private:
	StackOnly()
		:_a(0)
	{}
private:
	int _a;
};
int main()
{
	StackOnly p = StackOnly:: CreateObj();
	StackOnly a = new StackOnly(p);
	static StackOnly b = StackOnly(move(p));
}

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

C++98

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

class NonInherit
{
public:
	static NonInherit GetInstance()
	{
		return NonInherit();
	}
private:
	NonInherit()
	{}
};

C++11

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

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

5.只能创建一个对象的类 (单例模式)

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

怎么实现单例模式呢?

私有化构造函数,不能在类外创建对象。在类中定义一个静态对象(保证只创建一个对象),再定义静态函数来返回对象。

1.饿汉模式

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()
	{
		cout << "InfoMgr()" << endl;
	}
	
private:
	//对象信息
	string _ip = "127.0.0.1";
	int _port = 80;
	size_t _buffSize = 1024 * 1024;
	//...
	//对象声明
	static InfoMgr _ins;
};
//对象定义
InfoMgr InfoMgr::_ins;

不过有没有调用函数获取对象信息,都会创建对象。就像一个饿汉一开始就要吃东西。

懒汉模式

class InfoMgr
{
public:
	static InfoMgr& GetInstance()
	{
		if (p_ins == nullptr)
		{
			p_ins= p_ins = new InfoMgr;
		}
		return *p_ins;
	}

	void Print()
	{
		cout << _ip << endl;
		cout << _port << endl;
		cout << _buffSize << endl;
	}
private:
	//私有化构造函数
	InfoMgr(const InfoMgr&) = delete;
	InfoMgr& operator=(const InfoMgr&) = delete;
	InfoMgr()
	{
		cout << "InfoMgr()" << endl;
	}
	
private:
	//对象信息
	string _ip = "127.0.0.1";
	int _port = 80;
	size_t _buffSize = 1024 * 1024;
	//...
	//对象声明
	static InfoMgr* p_ins;
};
//对象定义
InfoMgr* InfoMgr::p_ins=nullptr;

int main()
{
	InfoMgr::GetInstance().Print();
}

懒汉模式是等到要调用函数的时候才会进行初始化对象。

类型转换

内置类型和内置类型

1.隐式转换(不同类型间有连系,int double long 都是计数的)

int i = 1;
// 隐式类型转换
double d = i;

2.显示类型转换(没有绝对连系)

int* p = &i;
// 显示的强制类型转换
int address = (int) p

自定义类型和内置类型

1.内置类型转自定义类型

class A
{
public:
	A(int a)
		:_a1(a)
		,_a2(a)
	{}
	A(int a,int b)
		:_a1(a)
		, _a2(b)
	{}
private:
	int _a1;
	int _a2;
};

int main()
{
	string s = "1111";
	//单参数隐式类型转换
	A a = 1;
	//多参数隐式类型转换
	A b = { 1,2 };
}

内置类型转为自定义类型的关键在于是否有对应的构造函数。

2.自定义类型转为内置类型

可以转,需要进行运算符重载,但()被仿函数重载,所以我们一般写成operator int() 不需要写返回类型,int就可以代替。如果需要返回double,把int 改为double就行。

class A
{
public:
	A(double a)
		:_a1(a)
		,_a2(a)
	{}
	A(double a, double b)
		:_a1(a)
		, _a2(b)
	{}
	operator double()
	{
		return _a1 + _a2;
	}
private:
	double _a1;
	double _a2;
};

int main()
{
	string s = "1111";
	//单参数隐式类型转换
	A a = 1;
	//多参数隐式类型转换
	A b = { 1.1,2.2 };
	double c = b;
	cout << c;
}

explicit可以禁止隐式类型转换

自定义类型和自定义类型

于自定义类型建立联系就要通过构造函数。

class A
{
public:
	A(int a)
		:_a1(a)
		,_a2(a)
	{}
	A(int a, int b)
		:_a1(a)
		, _a2(b)
	{}
	//加explicit禁止隐式类型转换 强制类型转换可以
	explicit operator int()
	{
		return _a1 + _a2;
	}
	int get() const
	{
		return _a1 + _a2;
	}
private:
	int _a1;
	int _a2;
};
class B
{
public:
	B(const A& a)
		:_b(a.get())
	{}
private:
	int _b;
};
int main()
{
	A a = { 1,2 };
	B b = a;

	B& c = b;
	//A隐式类型转换B 产生临时对象 常性+const
	const B& d = a;

}

四种命名的强制类型转换操作符:

1.static_cast

static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用
static_cast,但它不能用于两个不相关的类型进行转换。

int main()
{
	double d = 12.34;
	//相当于int a = d;隐式类型转换
	int a = static_cast<int>(d);
	cout << a << endl;
	return 0;
}

2 reinterpret_cast

reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型。(相当于强制类型转换)

3 const_cast

const_cast最常用的用途就是删除变量的const属性,方便赋值。

为什么cout<<a的值为2?

可以看到内存a中的值已经改变,但编译器进行了优化mov进去的值不是a是常数2.

我们可以对a加volatile(volatile 是 C++ 中的一个类型限定符,主要用于告诉编译器某个变量的值可能会在程序的其他部分被改变,因此编译器不应该对其进行优化。)

加了volatile 编译器不会进行优化,mov进去的值是内存中的a

4 dynamic_cast

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

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

可以看到无论是static_cast隐式/reinterpret显示类型转换,都可以编译通过,为什么还要用dynamic_case进行转换呢?

因为fun(A* pa)中pa可能是父类A传过来,还有可能是子类B传过来的。如果是父类传过来的B*pb访问子类新增的成员变量时就会越界。

如果是A* ps是父类传过来的,并且把A* pa转为B*,访问子类的新增成员就会出现越界访问的问题。简单来说就是因为B*pb指向的范围大于A*pa指向的范围导致越界访问。

所以建议用dynamic_cast,转换不成功就返回nullptr。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值