Detailed explanation of four kinds of C++ cast

1, The significance of cast

1.C + + inherits and extends the traditional type conversion method of C language, providing a more powerful transformation mechanism (inspection and risk)
2. Better position the place of transformation (ctrl+F cast)

2, reinterpret_cast

reinterpret_cast is the most powerful of the four kinds of coercion (the most violent, the lowest, and the most insecure). Its essence is the instructions of the compiler.
Its function: it can convert a pointer to an integer, or an integer to a pointer. Or the substitution of different types of pointers
Code example:

#include <iostream> 
int main()
{
	double a = 1.1;
	char * c = reinterpret_cast<char*>(&a);
	double* b = reinterpret_cast<double*>(c);
	printf("%lf",*b);
}

Operation results:

Analysis: we turn double through char * in the middle, but there is no accuracy problem. In fact, reinterpret_cast is only converted by the compiler. As long as it is an address, it can be converted (binary copy).

3, const_cast

There are two functions, remove const and add const
Add const:
code:

#include <iostream> 
int main()
{
	int* a=new int(1);
	const int * b = const_cast<const int * >(a);
	*a=2;
	//*b=2;, Constants cannot be modified
	printf("%d\n",*a);
	printf("%d\n",*b);
}

Operation results:

analysis:
We found that the values are the same, but the addresses are different, indicating that it is not a simple transformation, but a deep copy to transform first

Remove const (there are many cases here, the deep copy and shallow copy are different, and the original address may not be returned after the transformation):
code:

#include <iostream> 
class A
{
	public:
	int num;
	A(int val = 100):num(val){}
	~A(){}
};
int main()
{
	//1.const modifies the pointer object and points to the original object 
	const A * pa1 = new A(200);
	A * cast_pa1 = const_cast<A * >(pa1);
	printf("1.const Modifier pointer to object\n");
	printf("%p\n",pa1); 
	printf("%p\n",cast_pa1); 
	
	//2.const modifier refers to the value of the pointer object and points to the original object
	A * const pa2 = new A(200);
	A * cast_pa2 = const_cast<A * >(pa2);
	printf("2.const Modifies the value pointing to the object\n");
	printf("%p\n",pa2); 
	printf("%p\n",cast_pa2);
	
	//3.const modifies both the pointer object and the value of the pointer object, pointing to the original object
	const A * const pa3 = new A(200);
	A * cast_pa3_1 = const_cast<A * >(pa3);
	const A * cast_pa3_2 = const_cast<A * >(pa3);
	A * const cast_pa3_3 = const_cast<A * >(pa3);
	printf("3.const Modifies both the pointer object and the value of the pointer object,Point to original object\n");
	printf("%p\n",pa3); 
	printf("%p\n",cast_pa3_1);
	printf("%p\n",cast_pa3_2);
	printf("%p\n",cast_pa3_3);
	
	//4.const modifies a common object and assigns a value to a common object without pointing to the original object 
	const A pa4;
	A cast_pa4 = const_cast<A &>(pa4);
	printf("4.const Decorate ordinary objects and assign values to ordinary objects\n");
	printf("%p\n",&pa4); 
	printf("%p\n",&cast_pa4);
	
	//5.const modifies an ordinary object and assigns it to a reference object, pointing to the original object
	const A pa5;
	A& cast_pa5 = const_cast<A& >(pa5);
	printf("5.const Modifies an ordinary object and assigns it to a reference object\n");
	printf("%p\n",&pa5); 
	printf("%p\n",&cast_pa5);
	
	// 6. const modifies the object. After removing the const attribute, the object pointer is assigned to the pointer to point to the original object 
	const A pa6;
	A * cast_pa6 = const_cast<A * >(&pa6); 
	printf("6. const Modify object, object pointer const Property is assigned to the pointer\n");
	printf("%p\n",&pa6); 
	printf("%p\n",cast_pa6);
	
	//7.const modifies a local variable and does not point to the original object
	const int pa7=1;
	int  cast_pa7_1 = const_cast<int&>(pa7); 
	int& cast_pa7_2 = const_cast<int&>(pa7);
	int* cast_pa7_3 = const_cast<int*>(&pa7);
	
	printf("6. const Modify object, object pointer const Property is assigned to the pointer\n");
	printf("%p\n",&pa7); 
	printf("%p\n",&cast_pa7_1);
	printf("%p\n",&cast_pa7_2);
	printf("%p\n",cast_pa7_3);
	cast_pa7_1=10;
	printf("%d,not changed\n",pa7);
	cast_pa7_2=100;
	printf("%d,not changed\n",pa7);
	*cast_pa7_3=1000;
	printf("%d,not changed\n",pa7);
}

Execution results:

analysis:
Remove the const of the object pointer and all the original objects
Remove the const of the general object. If it is assigned to the general object, it is a new object, otherwise it is all the original object
Remove const of local variables and all new objects

4, static_cast

effect:
1. Conversion between basic types
2. Convert void pointer to pointer of any basic type
3. Used for pointer or reference conversion between subclass and parent class with inheritance relationship

Conversion between basic types:
code:

#include <iostream> 
int main()
{
	double i=1.1;
	int a = static_cast<int>(i);
	printf("%d\n",a); 
	double b = static_cast<int>(a);
	printf("%lf\n",b);
}

Operation results:

Analysis: basic type conversion can be carried out, but the accuracy will be lost, which is similar to the forced conversion of C language. Follow reinterpret_cast is different reinterpret_cast is the forced copy and semantic conversion of the underlying binary without loss of accuracy.

Conversion of void pointer and other pointers;
code;

#include <iostream> 
int main()
{
	int *a = new int(1);
	void *v = static_cast<void *>(a);
	int *p = static_cast<int *>(v);
	*a=2;
	printf("%d\n",*a);
	printf("%d\n",*p);
	printf("%p\n",a); 
	printf("%p\n",p); 
}


Analysis: This is the conversion between void pointer and other types of pointers. The result is that the original address is pointed to. (conversion of normal type is not)

Conversion between subclass and parent:
code:

#include <iostream> 
using namespace std;
class A
{
	public:
	A(){}
	void foo()
	{
		cout<<"A!"<<endl;
	}
};
class B:public A
{
	public:
	B(){}	
	void foo()
	{
		cout<<"B!"<<endl;
	}
}; 
int main()
{
	A *a = new A();
	B * b = static_cast<B *>(a);
	b->foo();
	return 0;
}

Operation results:

This is a downward transition, which is unsafe, but why not report an error, because there is no unique B (member variable of B) in B. We're looking at something else
code:

#include <iostream> 
using namespace std;
class A
{
	public:
	A(){}
	void foo()
	{
		cout<<"A!"<<endl;
	}
};
class B:public A
{
	char b='c';
	public:
	B(){}	
	void foo()
	{
		cout<<b<<endl;
	}
}; 
int main()
{
	A * a = new A();
	a->foo();
	static_cast<B*>(a)->foo();
	return 0;
}

Operation results;

Analysis: an error occurred here. The unique member variable in B was not initialized (unsafe downward transformation was used)
static_ The class transformation of cast is similar to ordinary strong transformation, and exceptions can be thrown

5, dynamic_cast

dynamic_cast is used for pointer or reference conversion between class inheritance levels (mainly used for downward security conversion)
dynamic_ The security of cast's downward transformation is mainly reflected in RTTI
RTTI:
Runtime type identification. The program can use the pointers or references of the base class to check the actual derived type of the object to which these pointers or references refer (judge the pointer prototype)
RTTI provides two very useful operators: typeid and dynamic_ cast. (the three most important things, dynamic_cast, typeid and type_info)
typeid: the typeid function (a friend function of the type_info class, why? The purpose is to prevent the creation of the type_info object) is to let the user know what type the current variable is, and it can return a type_ Info, you can get the name and code of the class. typeid overloads type_ Info = = and= Can be used to determine whether two types are equal
1) typeid identifies a static type
When the operand in typeid is one of the following, the typeid operator indicates the static type of the operand, that is, the type at compile time.
(1) Type name
(2) A basic type of variable
(3) A concrete object (non pointer object)
(4) A dereference to a pointer to a class object that does not contain a virtual function
(5) A reference to a class object that does not contain a virtual function
The static type will not change during the running of the program, so it is not necessary to calculate the type during the running of the program. The type information can be deduced according to the static type of the operand at compile time. For example, in the following code fragment, the operands in typeid are static types:

code:

#include <iostream> 
#include <typeinfo> 
using namespace std;
class X  {
	public:
		X()
		{
			
		}
		void func()
		{
			
		}
}; 
class XX : public X  {
	public:
	XX()
	{
		
	}
	void func()
	{
			
	}
}; 
class Y  {
	public:
	Y()
	{
		
	}
	void func()
	{
			
	}
}; 
 
int main()
{
    int n = 0;
    XX xx;
    Y y;
    Y *py = &y;
 
    // int and XX are both type names
    cout << typeid(int).name() << endl;
    cout << typeid(XX).name() << endl;
    // n is the basic variable
    cout << typeid(n).name() << endl;
    // Although the class xx belongs to has virtual, XX is a concrete object
    cout << typeid(xx).name() << endl;
    // py is a pointer and belongs to the basic type
    cout << typeid(py).name() << endl;
    // py refers to the object of Y, but there is no virtual function for class y
    cout << typeid(*py).name() << endl;
    return 0;
}

2) typeid identifies polymorphic types
When the operand in typeid is one of the following cases, the typeid operator needs to calculate the type when the program runs, because the type of its operand cannot be determined at compile time.
(1) A dereference to a pointer to a class object containing a virtual function
(2) A reference to a class object containing a virtual function

code:

#include <iostream> 
#include <typeinfo> 
using namespace std;
class X
{
    public:
        X()
        {
            mX = 101;
        }
        virtual void vfunc()
        {
            cout << "X::vfunc()" << endl;
        }
    private:
        int mX;
};
class XX : public X
{
    public:
        XX():
            X()
        {
            mXX = 1001;
        }
        virtual void vfunc()
        {
            cout << "XX::vfunc()" << endl;
        }
    private:
        int mXX;
};
void printTypeInfo(const X *px)
{
    cout << "typeid(px) -> " << typeid(px).name() << endl;
    cout << "typeid(*px) -> " << typeid(*px).name() << endl;
}
int main()
{
    X x;
    XX xx;
    printTypeInfo(&x);
    printTypeInfo(&xx);
    return 0;
}

Operation results:

Finally, the real pointer prototype is judged
So the question is, how does typeid calculate this type information? This issue will be highlighted below.

Polymorphic types are distinguished by declaring one or more virtual functions in a class. Because in C + +, a class with polymorphism is a virtual function directly declared or inherited. The type information of the object of the polymorphic class is saved in the - 1 entry of the index of the virtual function table, which is a type_ The address of the info object, the type_ The info object holds the type information corresponding to the object, and each class (polymorphic) corresponds to a type_info object
In the case of multiple inheritance and virtual inheritance, a class has n (n > 1) virtual function tables, and the objects of this class also have n vptr s, which point to these virtual function tables respectively. However, the values of the items with an index of - 1 (the address of the type_info object) of all virtual function tables of a class are equal, that is, they all point to the same type_ Info object, so that no matter which base class pointer or reference object pointing to its derived class is used, the same type can be obtained through the corresponding virtual function table_ Info object to get the same type information.

dynamic_cast (exceptions can be thrown)

dynamic_cast realizes the secure downward transformation with the help of RTTI mechanism (NULL is returned if it cannot be transformed)
code:

#include <iostream> 
#include <typeinfo> 
using namespace std;
class X
{
    public:
        X()
        {
            mX = 101;
        }
        virtual ~X()
        {
        }
    private:
        int mX;
};
 
class XX : public X
{
    public:
        XX():
            X()
        {
            mXX = 1001;
        }
        virtual ~XX()
        {
        }
    private:
        int mXX;
};
 
class YX : public X
{
    public:
        YX()
        {
            mYX = 1002;
        }
        virtual ~YX()
        {
        }
    private:
        int mYX;
};
int main()
{
    X x;
    XX xx;
    YX yx;
 
    X *px = &xx;
    cout << px << endl;
 
    XX *pxx = dynamic_cast<XX*>(px); // Conversion 1
    cout << pxx << endl;
 
    YX *pyx = dynamic_cast<YX*>(px); // Conversion 2
    cout << pyx << endl;
 
    pyx = (YX*)px; // Conversion 3
    cout << pyx << endl;
 
    pyx = static_cast<YX*>(px); // Conversion 4
    cout << pyx << endl;
 
    return 0;
}

Operation results:

analysis:
px is a pointer to a base class (X), but it points to an object of derived class xx. In conversion 1, the conversion succeeded because the object pointed to by px is indeed an object of XX. In conversion 2, the conversion fails because the object pointed to by px is not a YX object. At this time, dymanic_cast returns NULL. Conversion 3 is a C-style type conversion, while conversion 4 uses the static type conversion in C + +. They can convert successfully, but this object is not actually a YX object. Therefore, in conversion 3 and conversion 4, if you continue to use this object through the pointer, it will inevitably lead to errors, so this conversion is unsafe.

Declaration: the reference is slightly different from the pointer. When it fails, it does not return NULL, but throws a bad_cast exception because a reference cannot reference NULL.

Tags: C++ Back-end

Posted on Thu, 04 Nov 2021 14:15:59 -0400 by moriman