当前位置: 首页 > 工具软件 > Go Cast > 使用案例 >

C++ 强制类型转换操作符(static_cast、dynamic_cast、const_cast和reinterpret_cast)

郭璞
2023-12-01

C++中的四种操作符形式类型转换

1、static_cast (静态类型转换)

主要使用场景:适用于将void*转换为其他的指针

int a = 100;
void* pv = &a;
//int *pi = pv;  //不能隐式转换,只能显示转换
int *pi = static_case<int *>(pv);
*pi = 50;
cout << a << endl;  // 可以看到输出结果为50

思考:如果如果指针或引用指向的已经是const属性内存空间(只读),使用static_cast转换呢?

const int a = 100;
void* pv = &a;
//int *pi = pv;     //不能隐式转换,只能显示转换
int *pi = static_case<int *>(pv); 
*pi = 50;           // 此处不会报错
cout << a << endl;  // 但输出结果为100。因为内存为变量a分配的空间为只读
 
==》MSDN
 
static_cast Operator
 
The expression static_cast < type-id > ( expression ) converts expression to the type of type-id
based solely on the types present in the expression. No run-time type check is made to ensure
the safety of the conversion.
// static_cast 转换的类型 由 <type_id>的类型决定, 在转换过程中不进行运行时类型识别以保证转换的安全性
 
Syntax
static_cast < type-id > ( expression )
The static_cast operator can be used for operations such as converting a pointer to a base class
to a pointer to a derived class. Such conversions are not always safe. For example:
 
class B { ... };
class D : public B { ... };
void f(B* pb, D* pd)
{
D* pd2 = static_cast<D*>(pb); // not safe, pb may point to just B 这种基类指针转子类对象指针的方式(我们通常称为“向上转换”或者“向上造型”),通常采用dynamic_cast 以保证安全性
B* pb2 = static_cast<B*>(pd); // safe conversion  子类转基类则是安全的
...
}
 
In contrast to dynamic_cast, no run-time check is made on the static_cast conversion of pb.
The object pointed to by pb may not be an object of type D, in which case the use of *pd2 could
be disastrous. For instance, calling a function that is a member of the D class, but not the B
class, could result in an access violation.
 
The dynamic_cast and static_cast operators move a pointer throughout a class hierarchy.
However, static_cast relies exclusively on the information provided in the cast statement and
can therefore be unsafe. For example:
 
class B { ... };
class D : public B { ... };
void f(B* pb)
{
D* pd1 = dynamic_cast<D*>(pb);
D* pd2 = static_cast<D*>(pb);
}
 
If pb really points to an object of type D, then pd1 and pd2 will get the same value. They will
also get the same value if pb == 0.
 
If pb points to an object of type B and not to the complete D class, then dynamic_cast will know
enough to return zero. However, static_cast relies on the programmer’s assertion that pb points
to an object of type D and simply returns a pointer to that supposed D object.
 
Consequently, static_cast can do the inverse of implicit conversions, in which case the results
are undefined. It is left to the programmer to ensure that the results of a static_cast conversion
are safe.
 
This behavior also applies to types other than class types. For instance, static_cast can be used
to convert from an int to a char. However, the resulting char may not have enough bits to hold
the entire int value. Again, it is left to the programmer to ensure that the results of static_cast  
conversion are safe.
 
The static_cast operator can also be used to perform any implicit conversion, including standard
conversions and user-defined conversions. For example:
typedef unsigned char BYTE
 
void f()
{
char ch;
int i = 65;
float f = 2.5;
double dbl;
ch = static_cast<char>(i); // int to char
dbl = static_cast<double>(f); // float to double
...
i = static_cast<BYTE>(ch);
...
}
 
The static_cast operator can explicitly convert an integral value to an enumeration type. If the
value of the integral type does not fall within the range of enumeration values, the resulting
enumeration value is undefined.
 
The static_cast operator converts a null pointer value to the null pointer value of the destination
type.
 
Any expression can be explicitly converted to type void by the static_cast operator. The
destination void type can optionally include the const, volatile, or __unaligned attribute.
The static_cast operator cannot cast away the const, volatile, or __unaligned attributes. See
const_cast Operator for information on removing these attributes.

 通过上面MSDN的说明文档,我们可以知道,表达式static_cast < type-id > ( expression ) 用于进行类型的转换,但是不会进行运行时类型识别,因此使用他并不总是安全的,要注意使用场景

2、dynamic_cast(动态类型转换)

dynamic_cast 类型转换的适用场景: 主要用于具有父子类关系的,父类对象指针(或引用)转换成子类对象指针(或者引用)(“向上转换”)的场景

dynamic_cast Operator

The expression dynamic_cast<type-id>( expression ) converts the operand expression to an object of type type-id. The type-id must be a pointer or a reference to a previously defined class type or a “pointer to void”. The type of expression must be a pointer if type-id is a pointer, or an l-value if type-id is a reference.

Syntax

dynamic_cast < type-id > ( expression )

If type-id is a pointer to an unambiguous accessible direct or indirect base class of expression, a pointer to the unique subobject of type type-id is the result. For example:

class B { ... };
class C : public B { ... };
class D : public C { ... };

void f(D* pd)
{
   C* pc = dynamic_cast<C*>(pd);   // ok: C is a direct base class
                           // pc points to C subobject of pd 

   B* pb = dynamic_cast<B*>(pd);   // ok: B is an indirect base class
                           // pb points to B subobject of pd 
   ...
}

This type of conversion is called an “upcast” because it moves a pointer up a class hierarchy, from a derived class to a class it is derived from. An upcast is an implicit conversion.

If type-id is void*, a run-time check is made to determine the actual type of expression. The result is a pointer to the complete object pointed to by expression. For example:

class A { ... };

class B { ... };

void f()
{
   A* pa = new A;
   B* pb = new B;
   void* pv = dynamic_cast<void*>(pa);
   // pv now points to an object of type A
   ...
   pv = dynamic_cast<void*>(pb);
   // pv now points to an object of type B
}

If type-id is not void*, a run-time check is made to see if the object pointed to by expression can be converted to the type pointed to by type-id.

If the type of expression is a base class of the type of type-id, a run-time check is made to see if expression actually points to a complete object of the type of type-id. If this is true, the result is a pointer to a complete object of the type of type-id. For example:

class B { ... };
class D : public B { ... };

void f()
{
   B* pb = new D;               // unclear but ok
   B* pb2 = new B;

   D* pd = dynamic_cast<D*>(pb);      // ok: pb actually points to a D
   ...
   D* pd2 = dynamic_cast<D*>(pb2);   //error: pb2 points to a B, not a D
                              // pd2 == NULL
   ...
}

This type of conversion is called a “downcast” because it moves a pointer down a class hierarchy, from a given class to a class derived from it.

In cases of multiple inheritance, possibilities for ambiguity are introduced. Consider the class hierarchy shown in Figure 4.5:

Figure 4.5   Class Hierarchy Showing Multiple Inheritance

 

A pointer to an object of type D can be safely cast to B or C. However, if D is cast to point to an A object, which instance of A would result? This would result in an ambiguous casting error. To get around this problem, you can perform two unambiguous casts. For example:

void f()
{
   D* pd = new D;
   A* pa = dynamic_cast<A*>(pd);      // error: ambiguous
   B* pb = dynamic_cast<B*>(pd);      // first cast to B
   A* pa2 = dynamic_cast<A*>(pb);   // ok: unambiguous
}

Further ambiguities can be introduced when you use virtual base classes. Consider the class hierarchy shown in Figure 4.6:

Figure 4.6   Class Hierarchy Showing Virtual Base Classes

 

In this hierarchy, A is a virtual base class. See Virtual Base Classes for the definition of a virtual base class. Given an instance of class E and a pointer to the A subobject, a dynamic_cast to a pointer to B will fail due to ambiguity. You must first cast back to the complete E object, then work your way back up the hierarchy, in an unambiguous manner, to reach the correct B object.

Consider the class hierarchy shown in Figure 4.7:

Figure 4.7   Class Hierarchy Showing Duplicate Base Classes

 

Given an object of type E and a pointer to the D subobject, to navigate from the D subobject to the left-most A subobject, three conversions can be made. You can perform a dynamic_cast conversion from the D pointer to an E pointer, then a conversion (either dynamic_cast or an implicit conversion) from E to B, and finally an implicit conversion from B to A. For example:

void f(D* pd)
{
   E* pe = dynamic_cast<E*>(pd);
   B* pb = pe;      // upcast, implicit conversion
   A* pa = pb;      // upcast, implicit conversion
}

The dynamic_cast operator can also be used to perform a “cross cast.” Using the same class hierarchy, it is possible to cast a pointer, for example, from the B subobject to the D subobject, as long as the complete object is of type E.

Considering cross casts, it is actually possible to do the conversion from a pointer to D to a pointer to the left-most A subobject in just two steps. You can perform a cross cast from D to B, then an implicit conversion from B to A. For example:

void f(D* pd)
{
   B* pb = dynamic_cast<B*>(pd);      // cross cast
   A* pa = pb;                  // upcast, implicit conversion
}

A null pointer value is converted to the null pointer value of the destination type by dynamic_cast.

When you use dynamic_cast < type-id > ( expression ), if expression cannot be safely converted to type type-id, the run-time check causes the cast to fail. For example:

class A { ... };

class B { ... };

void f()
{
   A* pa = new A;
   B* pb = dynamic_cast<B*>(pa);      // fails, not safe; 
                              // B not derived from A
   ...
}

The value of a failed cast to pointer type is the null pointer. A failed cast to reference type throws a bad_cast exception.


Send feedback to MSDN.Look here for MSDN Online resources.

 

3、const_cast(常类型转换)

使用场景:主要用于去除一个指针或引用的常属性

 const int a =10;
 const int *pa =&a;
 *pa = 20;//error
 int *p2 =const_cast<int *>(pa);
 *p2 = 20;//ok

==》MSDN

const_cast Operator

The const_cast operator can be used to remove the const, volatile, and __unaligned attribute(s) from a class.

Syntax

const_cast < type-id > ( expression )

A pointer to any object type or a pointer to a data member can be explicitly converted to a type that is identical except for the const, volatile, and __unaligned qualifiers. For pointers and references, the result will refer to the original object. For pointers to data members, the result will refer to the same member as the original (uncast) pointer to data member. Depending on the type of the referenced object, a write operation through the resulting pointer, reference, or pointer to data member might produce undefined behavior.

The const_cast operator converts a null pointer value to the null pointer value of the destination type.


Send feedback to MSDN.Look here for MSDN Online resources.

 

4、reinterpret_cast(重解释类型转换)

 主要使用场景:

1、在指针和整型数之间的转换
2、任意的指针或引用之间的转换

3、以上三个操作符都无法完成的类型转换,可使用reinterpret_cast重解释类型转换

Operation: reinterpret_cast

Robert Schmidt
Microsoft Corporation

June 1, 2000

Last time I discussed the conversion operator static_cast. This time, I cover the companion operator reinterpret_cast, and give some guidance on when to use each.

While the Standard gives static_cast a sweeping general property plus a complicated set of exceptions, it limits reinterpret_cast to two fundamental roles:

  • Conversions to and from pointers


     
  • Overlaying an lvalue with multiple types (a.k.a. type punning)

As its name suggests, this style of cast reinterprets its operand's representation as having the target type. This reinterpretation involves no calls to conversion constructors or conversion operators; indeed, a reinterpret_cast may leave the operand's bit pattern intact, so that the conversion is purely a compile-time act with no run-time consequences.

Pointer Conversions

The set of pointer-specific conversions allowed by reinterpret_cast is fairly narrow:

  • From a pointer to an integer type.


     
  • From an integer or enumeration type to a pointer.


     
  • Among pointers to objects, functions, or members.

Provided that object-size and alignment guarantees are met (as I'll discuss below), the circular conversion sequence

T1 x1;
T2 x2;

x2 = reinterpret_cast<T2>(x1);
x1 = reinterpret_cast<T1>(x2);

restores x1's original value. Beyond this guarantee, conversion effects are not specified. In particular, conversions might change the underlying bit representation of the converted value, or they might leave the bit pattern intact. In the case of the latter, a reinterpret_cast truly represents a reinterpretation—but not an actual change—of an expression's bit pattern.

That all of these conversions involve pointers should not surprise you. On a given machine, the underlying representation among kindred pointers is often identical. Pointers, therefore, offer an ideal opportunity for static type conversions that don't actually require a run-time modification.

Like static_cast, reinterpret_cast cannot remove cv-qualification. The C++ committee members clearly want you to use const_cast for such conversions.

Converting Pointers To/From Non-Pointers

For a pointer to convert to an integer type, that integer type must be large enough to hold the pointer's value. The actual conversion is implementation-defined, although as the Standard notes, the conversion "is intended to be unsurprising to those who know the addressing structure of the underlying machine."

Going the other way, a value of integer or enumeration type can convert to a pointer. Further, a pointer converted to an integer and back will retain its original value—assuming the integer is large enough to hold the pointer. Otherwise, the mapping is implementation-defined.

(For those living in the Wonderful World of Windows, these are the rules that let you convert pointers to DWORDs and back again with impunity.)

One special value is the null pointer constant, which is implemented as an integer constant expression of value zero. Null pointer constants always convert to a null pointer of the target type. In a subtle distinction, other integer expressions of value zero may or may not convert to a null pointer:

#define NULL 0

const int i1 = 0;
      int i2 = 0;

reinterpret_cast<void *>(NULL);  // yields null pointer
reinterpret_cast<void *>(i1);    // yields null pointer
reinterpret_cast<void *>(i2);    // may or may not yield null pointer

Converting Among Pointers

Pointers to the same "kind" of entity—object, function, member object, or member function—can convert to one another, with the following caveats and restrictions:

  • As usual, the destination type cannot be less cv-qualified than the original type.


     
  • When converting among pointers to objects or member objects, the destination object type cannot have stricter alignment requirements than the original object type.


     
  • Null pointer values of the original type are converted to null pointer values of the destination type.

The following incomplete program shows examples of each conversion kind:

class T1;
class T2;

//
//  pointer to object
//
typedef T1 *O1;
typedef T2 *O2;

O1 o1;
reinterpret_cast<O2>(o1);

//
//  pointer to function
//
typedef T1 (*F1)();
typedef T2 (*F2)();

F1 f1;
reinterpret_cast<F2>(f1);

//
//  pointer to member object
//
typedef int  T1:: *MO1;
typedef long T2:: *MO2;

MO1 mo1;
reinterpret_cast<MO2>(mo1);

//
//  pointer to member function
//
typedef void (T1:: *MF1)();
typedef void (T2:: *MF2)();

MF1 mf1;
reinterpret_cast<MF2>(mf1); // OK

Based on my reading of both the C99 and C++ Standards, pointers of different kinds cannot convert to one another. Assuming I'm right, the conversions

reinterpret_cast<F1>(o1);
reinterpret_cast<O1>(f1);

should not compile. However, all of the translators I've tried—including EDG's in strict mode—allow these conversions. Fortunately, these non-Standard conversions appear to be pure extensions, meaning programs that don't use them aren't affected by them.

If you use such cross-kind pointer conversions, be aware that your code is non-conformant and thus non-portable. (Although if every compiler you care about supports this extension, then your code is portable in practice.)

Type Overlays

In addition to the pointer conversions I've shown above, reinterpret_cast also lets you overlay an lvalue with an arbitrary type interpretation. Specifically, you can bind a T1 lvalue to a T2 reference if you can reinterpret a T1 address as a T2 address. Given the declaration

T1 x1;

the cast

reinterpret_cast<T2 &>(x1);

is allowed if the conversion

reinterpret_cast<T2 *>(&x1);

is also allowed (assuming that & and * represent the built-in operators).

The reference cast is tantamount to dereferencing the pointer cast, so that

reinterpret_cast<T2 &>(x1)

and

*reinterpret_cast<T2 *>(&x1)

are equivalent. You can think of

reinterpret_cast<T2 &>(x1);

as &x1 being reinterpreted as a T2 *, then dereferenced into a T2 lvalue—even though the syntax suggests that x1 is directly reinterpreted as a T2 lvalue. This correlation between pointers and references shouldn't surprise you, since references are typically implemented as pointers under the hood. If a compiler allows a conversion to a T2 *, it can reinterpret that T2 * as its collateral T2 &, since the T2 * and T2 & probably share the same representation.

Because the cast operator doesn't directly reinterpret its operand's representation, I find the name reinterpret_cast to be somewhat misleading here. (Compare this cast to the earlier pointer-conversion examples, where the operand's representation was literally being reinterpreted.) When the C++ committee members "overloaded" reinterpret_cast, they took advantage of an implementation symbiosis: The underlying pointer-reinterpretation mechanism happens to permit reference-reinterpretation as well, even though the concepts are logically distinct. Whether they ought to have taken that advantage, in lieu of creating a fifth conversion operator or banning type puns outright, is another matter.

Unlike the other examples of reinterpret_cast, the T2 & conversion does not require that T1 and T2 preserve their values during a circular conversion, or that they even be convertible at all. Indeed, only pointers to those types must be convertible. Furthermore, since the T2 & binds directly to the original x1 object, no temporaries or other objects are created, and no conversion operators or constructors are called.

Punning

Programmers often call this casting technique "type punning." Like English puns, type puns allow multiple meanings for the same entity. In this way, punning is analogous to polymorphism, with two crucial differences:

  • Polymorphic types must have an inheritance relationship. Punned types can be completely unrelated, even incongruous.


     
  • Polymorphism relies on run-time object identification and conversion, which can induce significant run-time space and speed cost. Punning has minimal or no run-time cost. (The only potential cost is that of a pointer-to-pointer conversion; depending on the implementation, such a conversion may require no run-time resource.)

Unions are actually a closer analogy, since both unions and puns

  • Overlay an arbitrary set of types atop the same storage


     
  • Let you set the storage as one type, and reference it as another


     
  • Bypass the language's type-conversion rules (and risk undefined behavior)

To understand this analogy more clearly, ponder the effects of both

union
   {
   T1 x1;
   T2 x2;
   // ...
   Tn xn;
   } u;

u.x1; // treats the object as if it had type T1
u.x2; // treats the object as if it had type T2
// ...
u.xn; // treats the object as if it had type Tn

and

T1  x1;
T2 &x2 = reinterpret_cast<T2 &>(x1);
// ...
Tn &xn = reinterpret_cast<Tn &>(x1);

x1;   // treats the object as if it had type T1
x2;   // treats the object as if it had type T2
// ...
xn;   // treats the object as if it had type Tn

Punning Consequences

Punning lets you glue as many differently typed faces as you want onto the same object. For the technique to be maximally effective, you must understand the consequences of treating an object as if it were really of another type. Consider the example

#include <iostream>
using namespace std;

int main()
   {
   unsigned long   x1 = 0x12345678;
   unsigned short &x2 = reinterpret_cast<unsigned short &>(x1);
   cout << hex << x1 << endl;
   cout << hex << x2 << endl;
   x2 = 0xABCD;
   cout << hex << x1 << endl;
   cout << hex << x2 << endl;
   }

When run with Visual C++, this program produces

12345678
1234
abcd5678
abcd

Because the actual x1 object is bigger than what x2 believes it references, accesses through x2 "see" only part of x1. Similarly, changes through x2 overwrite part—but not all—of x1. The program is therefore safe, since x2's access stays within x1's bounds.

Now switch the definitions, so that the actual x1 object's type is too short. The modified program becomes

#include <iostream>
using namespace std;

int main()
   {
   unsigned short  x1 = 0x1234;
   unsigned long  &x2 = reinterpret_cast<unsigned long &>(x1);
   cout << hex << x1 << endl;
   cout << hex << x2 << endl;
   x2 = 0xABCDEFAB;
   cout << hex << x1 << endl;
   cout << hex << x2 << endl;
   }

The new results, at least on my machine, are

1234
12340003
abcd
abcdefab

x1 is now smaller than what x2 thinks it's bound to. As a result, access through x2 fetches memory beyond x1's borders; on my machine, that extra non-x1 memory shows up as 0x0003. The corresponding assignment through x2 scribbles beyond the limits of the x1 object. The result is undefined program behavior. In my case, the program didn't bomb; your mileage may vary.

Comparison and Contrast

Although I have yet to formally describe const_cast and dynamic_cast, I'll still include them in my (somewhat imprecise) rules of thumb for conversion operators:

  • If you reduce cv-qualification, use const_cast.


     
  • If you downcast, and want run-time assurance that the cast is valid, use dynamic_cast.


     
  • If you reinterpret a bit pattern, and a pointer is involved, use reinterpret_cast.


     
  • If you engage in type punning, use reinterpret_cast.


     
  • For all other conversions, use static_cast.


     
  • Avoid C casts of the form (T) e and functional casts of the form T(e). The conversion operators are both more obvious and more precise.

General Run-Time Behavior

reinterpret_cast traffics only in integers, enumerations, pointers, and references. It doesn't call user-defined functions, and probably doesn't call into the run-time library. At worst, a pointer conversion might require a few machine instructions to widen or narrow its operand. (While a compiler may package these conversions in library routines, I don't know of a compiler that actually does so.)

In contrast, static_cast converts among all of the above as well as floating-point and class types. Its run-time behavior is very much tied to the types being converted. For example, the conversion

int i;
static_cast<X>(i);

has several possible effects, depending on the type of X:

  • If X is a pointer, integer, or enumeration, the effect is tantamount to a reinterpret_cast.


     
  • If X is a floating-point type, i is converted to X. Depending on the implementation, this conversion may require inline floating-point instructions, calls to floating-point library routines, or hand-off to a separate floating-point processor.


     
  • If X is a class, i is the argument to a suitable X constructor, which must be declared and accessible.

Overlap

There is some overlap in the behavior of reinterpret_cast and static_cast. Both forms

  • Convert between void * and other pointer types


     
  • Convert between base and derived pointers


     
  • Bind a derived reference to a base object

Where either form will work, I recommend you stick with reinterpet_cast:

  • The name reinterpret_cast more precisely describes the transformation.


     
  • static_cast is one of the most dangerous tools in the C++ shed. You should reach for it only when nothing else will work.

I wish the committee had partitioned reintepret_cast and static_cast into mutually exclusive behavior sets. If the two operators had no intersection, I believe programmers could better understand when to use each. As things stand now, I suspect many programmers guess which to use, and let the compiler judge that choice.

Prevented Overlap

Conversely, there are behaviors that you might expect to overlap that don't. Consider the conversion

long l;
int i;

i = static_cast<int>(l);

With Visual C++ targeting Win32, long and int have identical representations. The conversion from long to int is really a reinterpretation of the existing long value. Indeed, the circular conversion

i = static_cast<int>(l);
l = static_cast<long>(i);

restores the original l value, thereby matching a key guarantee of reinterpret_cast.

You might consequently expect that

i = reinterpret_cast<int>(l);

should work as well, and that

i = reinterpret_cast<int>(l);
l = reinterpret_cast<int>(i);

should restore l's value. However, the language rules prevent these reinterpret_casts—even though they would have the same implementation as the static_casts.

The rules are not capricious. While the long-to-int conversion is a physical reinterpretation on Win32, it is not such a reinterpretation on all platforms. For example, Win16's ints and longs have different sizes. On that architecture, the conversion can change the long's value, so that a circular conversion might not restore the original value.

Bon Voyage

While I have yet to pin down my next column topic, I suspect I'm about to suffer great inspiration. The piece you're reading now is scheduled to go "live" on June 1. A few days after that, I'll be in Oregon attending Scott Meyers' STL seminar. I'm sure that trip will give me much to write about. (Reading through Scott's course materials, I've already jotted down enough ideas to cover me for months.)

Robert Schmidt is a technical writer for MSDN. His other major writing distraction is the C/C++ Users Journal, for which he is a contributing editor and columnist. In previous career incarnations he's been a radio DJ, wild-animal curator, astronomer, pool-hall operator, private investigator, newspaper carrier, and college tutor.

Deep C++ Glossary


Send feedback to MSDN.Look here for MSDN Online resources.

 类似资料: