Safer Typecasting Using ANSI C++ Typecasts

ANSI C++ adds four cast operators, with template-style syntax, that more clearly express the intentions of the programmer and make casts easier to spot in the code. These ANSI typecasts are:

  • static_cast<type>(expr) for converting between related types
  • const_cast<type>expr for casting away const or volatile
  • dynamic_cast<type>(expr) for safe navigation through an inheritance hierarchy
  • reinterpret_cast<type>(expr) for type conversions of pointers, between unrelated types

19.7.1. static_cast and const_cast

static_cast<DestType>(expr) converts the value expr to type DestType, provided that the compiler knows of an implicit conversion from expr to DestType. All type-checking is done at compile time.

static_cast('A' + 1.0);
static_cast(static_cast(y) + 1);

The static_cast operator converts between related types such as one pointer type to another, an enumeration type to an integral type, or a floating-point type to an integral type. These conversions are well defined, portable, and invertible. The compiler can apply some minimal type checking for each static_cast.

static_cast cannot cast away constness. For that you must use const_cast<DestType>(expr), which creates a non-const version of expr.

In that case, the DestType can differ from the type of expr only in the presence or absence of const/volatile.

For an int i, static_cast(i) will create a temporary of type double, which has the value of i. The variable i itself is not changed by this cast.

Example 19.3 contains both kinds of casts.

Example 19.3. src/ansicast/m2k.cpp

// Miles are converted to kilometers.
#include 

QTextStream cin(stdin, QIODevice::ReadOnly);
QTextStream cout(stdout, QIODevice::WriteOnly);
QTextStream cerr(stderr, QIODevice::WriteOnly);
const double m2k = 1.609; // conversion constant

inline double mi2km(int miles) {
 return (miles * m2k);
}

int main() {
 int miles;
 double kilometers;
 cout << "Enter distance in miles: " << flush;
 cin >> miles ;
 kilometers = mi2km(miles);
 cout << "This is approximately "
 << static_cast(kilometers)
 << "km."<< endl;
 cout << "Without the cast, kilometers = "
 << kilometers << endl;
 double* dp = const_cast(&m2k);
 cout << "m2k: " << m2k << endl;
 cout << "&m2k: " << &m2k << " dp: " << dp << endl;
 cout << "*dp: " << *dp << endl;
 *dp = 1.892; <-- 1
 cout << "Can we reach this statement? " << endl;
 return 0;
}

Output:

Enter distance in miles: 23
This is approximately 37km.
Without the cast, kilometers = 37.007
m2k: 1.609
&m2k: 0x8049048 dp: 0x8049048
*dp: 1.609
Segmentation fault
 

(1)What are we attempting to do here?

Here are some observations regarding the previous example.

  • The mixed expression miles * m2k is implicitly widened to double.
  • The safe cast static_cast(kilometres) truncates the double value to int.
  • The cast did not change the variable kilometres.
  • The results of our attempt to assign to *dp are undefined.

Casting Away const

In general, const_cast is only used for const-references and pointers to non-const objects. Using const_cast to change const objects has undefined behavior because const objects may be stored in read-only memory (which the operating system protects). In the case of const int, trying to change it by casting away const depends on compiler optimization techniques, which frequently optimize them out of existance (by doing pre-compilation value replacement). Consider Example 19.4.

Example 19.4. src/casts/constcast1.cpp

#include 
using namespace std;

int main() {
 const int N = 22;
 int * pN = const_cast(&N);
 *pN = 33;
 cout << N << '	' << &N << endl;
 cout << *pN << '	' << pN << endl;
}

Output:

22 0xbf91cfa0
33 0xbf91cfa0

The above output, obtained with gcc version 4.0.3, could be different on your system, because the behavior is undefined.

In this example we used const_cast to obtain a regular pointer to a const int. Because the const int is in stack storage class, we don't get a segmentation fault by attempting to change the memory. The compiler is unable to "optimize out" the int, and the const_cast tells it not to even try.

Exercises: static_cast and const_cast

1.

In Example 19.4, try moving the

const int N = 22;
 

above or below

int main() {
 

Observe and explain the difference in output.

2.

Predict the output of Example 19.5.

Remove the const_cast from the call to f2() inside f1(), and predict the output again.

Example 19.5. src/casts/constcast2.cpp

#include 

void f2(int& n) {
 ++n;
}

void f1(const int& n, int m) {
 if(n < m)
 f2(const_cast(n));
}

using namespace std;

int main() {
 int num1(10), num2(20);
 f1(num1, num2);
 cout << num1 << endl;
}

19.7.2. reinterpret_cast

reinterpret_cast is used for casts that are representation- or system-dependent; examples are conversions between unrelated types such as int to pointer or between unrelated pointer types such as int* to double*. It cannot cast away const. reinterpret_casts are dangerous, generally not portable, and should be avoided.

Consider the following situation.

Spam spam;
Egg* eggP;
eggP = reinterpret_cast(&spam);
eggP->scramble();

reinterpret_cast takes some spam and gives us an Egg-shaped pointer, without any concern for type compatibility.

By using eggP, we are reinterpreting the bits of spam as if they were bits of egg. In some countries, this would be sacrilege!

What Is It Really Used For?

Sometimes, a C function returns a void* pointing to a type that is known to the developer. In such a case, a typecast from void* to the actual type is needed. If you are sure it is pointing to an Egg, reinterpret_cast is the appropriate cast to use. There is no compiler or runtime checking on such a cast.

19.7.3. Why Not Use C-style Casts?

C-style casts are deprecated and should not be used anymore. Consider the following situation, quite similar to the previous example.

Apple apple;
Orange* orangeP;
// other processing steps ...
orangeP = (Orange*) &apple;
orangeP->squeeze();

The problem is that we can not tell from looking at this code whether the developer is aware that an Apple is not compatible with an Orange. From looking at it, it is unclear whether this is a proper type conversion or a non-portable pointer conversion.

Errors caused by such a cast can be very difficult to understand and correct. If a system-dependent cast is necessary, it is preferable to use reinterpret_cast over a C-style cast so that, when troubles arise, it will be easier to spot the likely source of those troubles in the code.


Part I: Introduction to C++ and Qt 4

C++ Introduction

Classes

Introduction to Qt

Lists

Functions

Inheritance and Polymorphism

Part II: Higher-Level Programming

Libraries

Introduction to Design Patterns

QObject

Generics and Containers

Qt GUI Widgets

Concurrency

Validation and Regular Expressions

Parsing XML

Meta Objects, Properties, and Reflective Programming

More Design Patterns

Models and Views

Qt SQL Classes

Part III: C++ Language Reference

Types and Expressions

Scope and Storage Class

Statements and Control Structures

Memory Access

Chapter Summary

Inheritance in Detail

Miscellaneous Topics

Part IV: Programming Assignments

MP3 Jukebox Assignments

Part V: Appendices

MP3 Jukebox Assignments

Bibliography

MP3 Jukebox Assignments



An Introduction to Design Patterns in C++ with Qt 4
An Introduction to Design Patterns in C++ with Qt 4
ISBN: 0131879057
EAN: 2147483647
Year: 2004
Pages: 268

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net