如果说
static_cast
是C++对C风格类型转换的继承,那么
dynaic_cast
可以说是完全脱离了C风格的类型转换,主要服务于C++的多态特性。学会正确的使用这种类型转换在大型软件开发的实践中可以说是非常的重要。
在大型软件中多态特性往往会被用到极致,
dynamic_cast
的使用可以帮助我们在开发过程中规避很多风险。
结论
dynamic_cast<new_type>(old_variable)
:多态类之间的类型转换使用该方法进行类型转换。
- 其中
new_type
的类型:指向某个class类型的指针、某个class类型的引用、void指针; - 使用条件:该class类型具有虚函数;
重点是该方法的返回值:
- . 基类转子类,返回nullptr(对于指针)或者std::bad_cast(对于引用);
- 子类转基类,返回基类指针(对于指针)或者基类指针(对于引用);
- 子类之间转换,返回nullptr(对于指针)或者std::bad_cast(对于引用);
事例
在具体讲解之前,准备三个具有继承关系的类,为之后说明做准备,我们还是用父亲老王、儿子小明、女儿小红来讨论这个问题。
老王有一个儿子小明和一个女儿小红,老王写了个遗嘱准备留下有遗产10块钱,这十块钱由儿子和女儿平分。
小明自己有11块,所以小明加上可以继承的钱总共16块钱;小红自己有12块钱,所以小红加上可以继承的钱总共有17块钱。
#pragma once
#include<iostream>
//父亲:老王
class LaoWang
{
public:
LaoWang() :m_LaoWangMoney(10) {}
~LaoWang() {}
virtual void money() {
std::cout << m_LaoWangMoney<< std::endl;
}
int m_LaoWangMoney;
};
//儿子:小明
class XiaoMing:public LaoWang
{
public:
XiaoMing() :m_XiaoMingMoney(11) {}
~XiaoMing() {}
void money() override {
std::cout << m_LaoWangMoney / 2 + m_XiaoMingMoney << std::endl;
}
private:
int m_XiaoMingMoney;
};
//女儿:小红
class XiaoHong:public LaoWang
{
public:
XiaoHong() :m_XiaoHongMoney(12) {}
~XiaoHong() {}
void money() override {
std::cout << m_LaoWangMoney / 2 + m_XiaoHongMoney << std::endl;
}
private:
int m_XiaoHongMoney;
};
dynamic_cast
通常的使用形式是:
new_type b = dynamic_cast < new_type > ( expression );
原理
dynamic_cast
依赖于C++的运行时类型识别,简单的说就是dynamic_cast
会遍历继承树,来判断是否有继承关系。
在类的虚表的前面存放RTTI数据块的指针。因此,类必须有虚函数,才会有RTTI。所以基类必须有虚函数,并且dynamic_cast
的类型必须是指针或者引用。
使用测试
我们以指针为例(在实际的开发过程中,也是指针使用的更多),看dynamic_cast
不同情形下的结果:
我们使用老王、小明、小红这三个类进行相互的转换,来测试dynamic_cast
的结果。
#include<iostream>
#include"class_use.h"
int main() {
LaoWang* lao_wang = new LaoWang();
XiaoMing* xiao_ming = new XiaoMing();
XiaoHong* xiao_hong = new XiaoHong();
//1. 父类到子类的转换,返回nullptr
XiaoMing* xiao_ming_2 = dynamic_cast<XiaoMing*>(lao_wang);
if (nullptr == xiao_ming_2) {
std::cout << "转换失败" << std::endl;
}
//2. 子类到父类的转换
LaoWang* lao_wang_2 = dynamic_cast<LaoWang*>(xiao_ming);
if (nullptr != lao_wang_2) {
lao_wang_2->money(); //打印出16;
}
else {
std::cout << "转换失败" << std::endl;
}
LaoWang* lao_wang_3 = dynamic_cast<LaoWang*>(xiao_hong);
if (nullptr != lao_wang_3) {
lao_wang_3->money(); //打印出17;
}
else {
std::cout << "转换失败" << std::endl;
}
//3. 子类相互转换
XiaoMing* xiao_ming_3 = dynamic_cast<XiaoMing*>(xiao_hong);
if (nullptr != xiao_ming_3) {
xiao_ming_3->money();
}
else {
std::cout << "转换失败" << std::endl;
}
delete lao_wang, xiao_ming, xiao_hong;
std::cin.get();
return 0;
}
结果如下:
使用结果与结论符合:
- 基类转子类,返回nullptr;
- 子类转基类,返回基类指针;
- 子类之间转换,返回nullptr;
std::dynamic_pointer_cast
C++11 以来,我们在实际使用中,我们经常遇到的需要转换是智能指针。智能指针的好处我也不多赘述,反正为了解决智能指针的类型转换问题,增加了std::dynamic_pointer_cast
方法,本质上就是一个模板函数。注意,该方法只适用于std::shared_ptr
、其他类型的智能指针不适用。
原理
该函数的原理如下,首先,把指针拿出来,然后通过dynamic转化指针类型,然后再生成对应的智能指针,三个步骤。下面是源代码:
#ifdef _CPPRTTI
template <class _Ty1, class _Ty2>
_NODISCARD shared_ptr<_Ty1> dynamic_pointer_cast(const shared_ptr<_Ty2>& _Other) noexcept {
// dynamic_cast for shared_ptr that properly respects the reference count control block
const auto _Ptr = dynamic_cast<typename shared_ptr<_Ty1>::element_type*>(_Other.get());
if (_Ptr) {
return shared_ptr<_Ty1>(_Other, _Ptr);
}
return {};
}
原理还是很简单的,如果需要对std::unique_ptr
进行转换,可以模仿这个方式自己写,在标准库中,并没有提供。
使用测试
我们还是通过老王、小明、小红这个例子进行测试:
#include<iostream>
#include"class_use.h"
#include<memory>
using LaoWang_Ptr = std::shared_ptr<LaoWang>;
using XiaoMing_Ptr = std::shared_ptr<XiaoMing>;
using XiaoHong_Ptr = std::shared_ptr<XiaoHong>;
int main() {
LaoWang_Ptr lao_wang = std::make_shared<LaoWang>();
XiaoMing_Ptr xiao_ming = std::make_shared<XiaoMing>();
XiaoHong_Ptr xiao_hong = std::make_shared<XiaoHong>();
//父类到子类的转换,返回nullptr
XiaoMing_Ptr xiao_ming_2 = std::dynamic_pointer_cast<XiaoMing>(lao_wang);
if (nullptr == xiao_ming_2) {
std::cout << "转换失败" << std::endl;
}
//子类到父类转换,返回nullptr
LaoWang_Ptr lao_wang_2 = std::dynamic_pointer_cast<LaoWang>(xiao_ming);
if (nullptr != lao_wang_2) {
lao_wang_2->money(); //打印出16;
}
else {
std::cout << "转换失败" << std::endl;
}
LaoWang_Ptr lao_wang_3 = std::dynamic_pointer_cast<LaoWang>(xiao_hong);
if (nullptr != lao_wang_3) {
lao_wang_3->money(); //打印出17;
}
else {
std::cout << "转换失败" << std::endl;
}
//子类相互转换
XiaoMing_Ptr xiao_ming_3 = std::dynamic_pointer_cast<XiaoMing>(xiao_hong);
if (nullptr != xiao_ming_3) {
xiao_ming_3->money();
}
else {
std::cout << "转换失败" << std::endl;
}
return 0;
}
最终结果与之前是一致的。
强调
最后强调一下最佳实践:在对多态类型转换的时候,一定要使用dynamic_cast
或者std::dynamic_pointer_cast
,注意判断转换结果是否为nullptr
,避免转换失败后通过空指针调用函数,程序直接崩溃。
格式如下:
new_type b = dynamic_cast<new_type>(a)
//这段判断可以写成宏,方便调用。
if(nullptr == b) {
//dosomething
}
else {
//dosomething
}
点赞收藏加关注,乱杀bug挡不住!