C++ learning: four types of mandatory conversions in C++ static_cast, dynamic_cast, const_cast, reinterpret_cast

1. C cast and c++ cast

C language mandatory type conversion is mainly used for conversion between basic data types, the syntax is:

(type-id) expression;  //转换格式1
type-id (expression);  //转换格式2

In addition to the mandatory type conversion of c language, c++ has added four new types of mandatory type conversion: static_cast, dynamic_cast, const_cast, reinterpret_cast, which are mainly used for mandatory conversion between inheritance relationship classes. The syntax is:

static_cast<new_type>      (expression)
dynamic_cast<new_type>     (expression) 
const_cast<new_type>       (expression) 
reinterpret_cast<new_type> (expression)

Note: new_type is the target data type, and expression is the original data type variable or expression.

In "Effective C++", the c language cast is called the old-style cast , and the c++ cast is called the new cast .

二、static_cast、dynamic_cast、const_cast、reinterpret_cast

      1:const_cast

const_cast is used to modify a const or volatile attribute of a type. This operator is used to modify a const (the only C++-style cast operator capable of this) or volatile attribute of a type. Except for const or volatile decoration, new_type and expression are of the same type.

①The constant pointer is converted into a non-constant pointer and still points to the original object;

②The constant reference is converted into a non-constant reference and still points to the original object;

③const_cast is generally used to modify the bottom pointer. Such as const char *p form.

An example conversion is as follows:

const int g = 20;
int *h = const_cast<int*>(&g);     //去掉const常量const属性

const int g = 20;
int &h = const_cast<int &>(g);     //去掉const引用const属性

 const char *g = "hello";
char *h = const_cast<char *>(g);   //去掉const指针const属性

2:dynamic_cast

dynamic_cast<type*>(e)
dynamic_cast<type&>(e)
dynamic_cast<type&&>(e)

type must be a class type, in the first form type must be a valid pointer, in the second form type must be an lvalue, in the third form type must be an rvalue. In all of the above forms, the type of e must meet any of the following three conditions: the type of e is a public derived class of the target type type, the type of e is a common base class of the target type, or the type of e is the type of the target type. If a dynamic_cast statement casts to a pointer type and fails, the result is 0. If the conversion target is a reference type and fails, the dynamic_cast operator will throw a std::bad_cast exception (the exception is defined in the typeinfo standard library header file). e can also be a null pointer, and the result is a null pointer of the desired type.

dynamic_cast is mainly used for up-conversion and down-conversion between class hierarchies, and can also be used for cross-cast between classes. 

The effect of dynamic_cast and static_cast is the same when up-converting between class hierarchies;

During downcasting, dynamic_cast has the function of type checking and is safer than static_cast. dynamic_cast is the only action that cannot be performed by the old-style syntax, and the only cast action that can have a significant runtime cost.

(1) Pointer type

For example, Base is a base class that contains at least one virtual function, Derived is a common derived class of Base, if there is a pointer bp pointing to Base, we can convert it into a pointer pointing to Derived at runtime, the code is as follows:

if(Derived *dp = dynamic_cast<Derived *>(bp)){
  //使用dp指向的Derived对象  
}
else{
  //使用bp指向的Base对象  
}

It is worth noting that in the above code, dp is defined in the if statement. The advantage of this is that two tasks of type conversion and condition checking can be completed in one operation.

(2) Reference type

Because there is no so-called null reference, the dynamic_cast conversion of the reference type is different from the pointer type. When the reference conversion fails, a std::bad_cast exception will be thrown, which is defined in the header file typeinfo.

void f(const Base &b){
 try{
   const Derived &d = dynamic_cast<const Base &>(b);  
   //使用b引用的Derived对象
 }
 catch(std::bad_cast){
   //处理类型转换失败的情况
 }
}

3:static_cast

static_cast is equivalent to the forced conversion in the traditional C language. This operator converts expression to new_type type and is used to force implicit conversion, such as converting non-const object to const object. Compile-time check is used for non-polymorphic conversion. It can convert pointers and others, but there is no runtime type check to ensure the safety of conversion . It mainly has the following usages:

①Used for the conversion of pointers or references between the base class (parent class) and derived class (subclass) in the class hierarchy.

Upcasting (converting a pointer or reference of a derived class to a base class representation) is safe ;

Downcasting (converting base class pointers or references to derived class representations) is unsafe because there is no dynamic type checking .

② Used for conversion between basic data types, such as converting int to char and converting int to enum. The security of this conversion should also be guaranteed by the developer.

③Convert the null pointer to a null pointer of the target type.

④ Convert any type of expression into void type.

Note: static_cast cannot convert const, volatile, or __unaligned attributes of expression.

Examples of basic type data conversion are as follows:

char a = 'a';
int b = static_cast<char>(a);//正确,将char型数据转换成int型数据

double *c = new double;
void *d = static_cast<void*>(c);//正确,将double指针转换成void指针

int e = 10;
const int f = static_cast<const int>(e);//正确,将int型数据转换成const int型数据

const int g = 20;
int *h = static_cast<int*>(&g);//编译错误,static_cast不能转换掉g的const属性

Class uplink and downlink conversion:

if(Derived *dp = static_cast<Derived *>(bp)){//下行转换是不安全的
  //使用dp指向的Derived对象  
}
else{
  //使用bp指向的Base对象  
}

if(Base*bp = static_cast<Derived *>(dp)){//上行转换是安全的
  //使用bp指向的Derived对象  
}
else{
  //使用dp指向的Base对象  
}

4: reinterpret_cast

new_type must be a pointer, reference, arithmetic type, function pointer, or member pointer. It can convert a pointer to an integer, or convert an integer to a pointer (first convert a pointer to an integer, then convert the integer to a pointer of the original type, and get the original pointer value).

reinterpret_cast is intended to perform a low-level cast, the actual action (and result) may depend on the editor, which means it is not portable .

  To give an example of using reintepret_cast wrongly, after converting the integer type into a function pointer, vc++ will report an "unhandled exception at 0xxxxxxxxx in...: 0xC0000005: Access violation" error during execution:

#include <iostream>
using namespace std;
int output(int p){
    cout << p <<endl;  return 0;
}

typedef int (*test_func)(int );//定义函数指针test_func
int main(){
    int p = 10;
    test_func fun1 = output;
    fun1(p);//正确
    test_func fun2 = reinterpret_cast<test_func>(&p);
    fun2(p);//...处有未经处理的异常: 0xC0000005: Access violation
    return 0;
}

IBM's C++ guide , the FAQ page of Bjarne Stroustrup, the father of C++, and MSDN's Visual C++ also point out that incorrect use of reinterpret_cast can easily lead to program insecurity, and only the converted type value is converted back to its original type, which is the correct way to use reinterpret_cast.

  It is also mentioned in MSDN that in practice, reinterpret_cast can be applied to the hash function, as follows (in 64-bit systems, unsigned int needs to be changed to unsigned long):

// expre_reinterpret_cast_Operator.cpp
// compile with: /EHsc
#include <iostream>

// Returns a hash code based on an address
unsigned short Hash( void *p ) {
   unsigned int val = reinterpret_cast<unsigned int>( p );
   return ( unsigned short )( val ^ (val >> 16));
}

using namespace std;
int main() {
   int a[20];
   for ( int i = 0; i < 20; i++ )
      cout << Hash( a + i ) << endl;
}

In addition, the difference between static_cast and reinterpret_cast is mainly in multiple inheritance, such as

class A {
    public:
    int m_a;
};
 
class B {
    public:
    int m_b;
};
 
class C : public A, public B {};

example:

C c;
printf("%p, %p, %p", &c, reinterpret_cast<B*>(&c), static_cast <B*>(&c));

The first two output values ​​are the same, and the last one will be offset by 4 bytes on the original basis. This is because static_cast calculates the offset of parent-child class pointer conversion and converts it to the correct address (c has m_a, m_b, which is converted to a B * pointer and points to m_b), while reinterpret_cast will not do this level of conversion.

 Therefore, you need to use reinterpret_cast with caution.

Precautions:

  • New-style conversions are preferred over old-style conversions. There are two reasons. One is that new-style transformations are easier to identify, which can simplify the process of "finding out where the type system is broken". Second, the narrower the goal of each transformation action, the better the compiler can diagnose wrong usage.
  • Try to use transformation operations as little as possible, especially dynamic_cast, which takes a long time and will lead to performance degradation. Try to use other methods instead.

Guess you like

Origin blog.csdn.net/weiweiqiao/article/details/131328873