[C/C + + foundation] (type conversion Series III) dynamic_cast and std::dynamic_pointer_castcast

Posted by svenski on Sat, 09 Oct 2021 13:15:00 +0200


if static_cast is the inheritance of C + + to C-style type conversion, so dynaic_cast can be said to be completely divorced from C-style type conversion and mainly serves the polymorphism of C + +. Learning to use this type conversion correctly is very important in the practice of large-scale software development.
In large-scale software, polymorphism is often used to the extreme, dynamic_ The use of cast can help us avoid many risks in the development process.

conclusion

dynamic_ cast<new_ Type > (old_variable): this method is used for type conversion between polymorphic classes.

  1. Where new_type: pointer to a class type, reference to a class type, void pointer;
  2. Usage condition: the class type has a virtual function;

The key point is the return value of the method:

  1. . base class rotor class, return nullptr (for pointer) or std::bad_cast (for references);
  2. Subclass to base class, return base class pointer (for pointer) or base class pointer (for reference);
  3. Convert between subclasses and return nullptr (for pointers) or std::bad_cast (for references);

example

Before the specific explanation, prepare three classes with inheritance relationship to prepare for the subsequent explanation. We still use father Lao Wang, son Xiao Ming and daughter Xiao Hong to discuss this problem.
Lao Wang has a son Xiaoming and a daughter Xiaohong. Lao Wang wrote a will to leave an inheritance of 10 yuan, which is divided equally between his son and daughter.
Xiao Ming has 11 yuan, so Xiao Ming and the money he can inherit are 16 yuan in total; Xiaohong has 12 yuan, so Xiaohong and the money she can inherit have a total of 17 yuan.

#pragma once
#include<iostream>

//Father: Lao Wang
class LaoWang
{
public:
	LaoWang() :m_LaoWangMoney(10) {}
	~LaoWang() {}

	virtual void money() {
		std::cout << m_LaoWangMoney<< std::endl;
	}

	int m_LaoWangMoney;

};

//Son: Xiao Ming
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;
};

//Daughter: Xiao Hong
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

The usual forms of use are:

new_type b = dynamic_cast < new_type > ( expression );

principle

dynamic_cast relies on C + + runtime type recognition, which is simply dynamic_cast will traverse the inheritance tree to determine whether there is an inheritance relationship.
Store the pointer of RTTI data block in front of the virtual table of class. Therefore, the class must have virtual functions to have RTTI. Therefore, the base class must have virtual functions and dynamic_cast must be of type pointer or reference.

Use test

Let's take pointers as an example (in the actual development process, pointers are more used) to see dynamic_ Results of cast in different cases:
We use Lao Wang, Xiao Ming and Xiao Hong to convert each other to test dynamic_ The result of 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. The conversion from parent class to child class returns nullptr
	XiaoMing* xiao_ming_2 = dynamic_cast<XiaoMing*>(lao_wang);
	if (nullptr == xiao_ming_2) {
		std::cout << "switch views" << std::endl;
	}

	//2. Conversion from subclass to parent class
	LaoWang* lao_wang_2 = dynamic_cast<LaoWang*>(xiao_ming);
	if (nullptr != lao_wang_2) {
		lao_wang_2->money();       //Print out 16;
	}
	else {
		std::cout << "switch views" << std::endl;
	}

	LaoWang* lao_wang_3 = dynamic_cast<LaoWang*>(xiao_hong);
	if (nullptr != lao_wang_3) {
		lao_wang_3->money();       //Print out 17;
	}
	else {
		std::cout << "switch views" << std::endl;
	}
  
  //3. Subclass conversion
	XiaoMing* xiao_ming_3 = dynamic_cast<XiaoMing*>(xiao_hong);
	if (nullptr != xiao_ming_3) {
		xiao_ming_3->money();
	}
	else {
		std::cout << "switch views" << std::endl;
	}

	delete lao_wang, xiao_ming, xiao_hong;
	std::cin.get();
	return 0;
}

The results are as follows:

The application results are consistent with the conclusions:

  1. Base class rotor class, return nullptr;
  2. Subclass to base class, return the base class pointer;
  3. Convert between subclasses and return nullptr;

std::dynamic_pointer_cast

Since C++11, in our practical use, we often encounter the need to convert smart pointers. I won't elaborate on the benefits of smart pointers. Anyway, in order to solve the type conversion problem of smart pointers, STD:: dynamic is added_ pointer_ Cast method is essentially a template function. Note that this method is only applicable to std::shared_ptr, other types of smart pointers are not applicable.

principle

The principle of this function is as follows: first, take out the pointer, then convert the pointer type through dynamic, and then generate the corresponding smart pointer in three steps. Here is the source code:

#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 {};
}

The principle is still very simple. If you need to modify std::unique_ptr for conversion, you can imitate this way to write your own, which is not provided in the standard library.

Use test

Let's take Lao Wang, Xiao Ming and Xiao Hong as examples to test:

#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>();


	//The conversion from parent class to child class returns nullptr
	XiaoMing_Ptr xiao_ming_2 = std::dynamic_pointer_cast<XiaoMing>(lao_wang);
	if (nullptr == xiao_ming_2) {
		std::cout << "switch views" << std::endl;
	}

	//Subclass to parent conversion, return nullptr
	LaoWang_Ptr lao_wang_2 = std::dynamic_pointer_cast<LaoWang>(xiao_ming);
	if (nullptr != lao_wang_2) {
		lao_wang_2->money();       //Print out 16;
	}
	else {
		std::cout << "switch views" << std::endl;
	}

	LaoWang_Ptr lao_wang_3 = std::dynamic_pointer_cast<LaoWang>(xiao_hong);
	if (nullptr != lao_wang_3) {
		lao_wang_3->money();       //Print out 17;
	}
	else {
		std::cout << "switch views" << std::endl;
	}
	
	//Subclass conversion
	XiaoMing_Ptr xiao_ming_3 = std::dynamic_pointer_cast<XiaoMing>(xiao_hong);
	if (nullptr != xiao_ming_3) {
		xiao_ming_3->money();
	}
	else {
		std::cout << "switch views" << std::endl;
	}

	return 0;
}

The final result is consistent with that before.

emphasize

Finally, highlight the best practice: when converting polymorphic types, be sure to use dynamic_cast or std::dynamic_pointer_cast, pay attention to judge whether the conversion result is nullptr, so as to avoid calling the function through the null pointer after the conversion fails, and the program will crash directly.
The format is as follows:

new_type b = dynamic_cast<new_type>(a)

//This judgment can be written into a macro for easy calling.
if(nullptr == b) {
   //dosomething
}
else {
  //dosomething
}


Like collection and pay more attention to random killing bug s!

Topics: C++