Type casting in C++ is a form of what is known in computer science as type punning – that is, circumventing the type system of a programming language.
C++ inherits its conversion and casting mechanism from C, but supplements it (although sensibly we should say, replaces it) with four, more explicit cast operations:
- static_cast
- reinterpret_cast
- const_cast
- dynamic_cast
In C and C++ – and particularly in embedded systems – casting is a necessary evil; so much so that many programmers just accept it as part of everyday programming practice.
So then: why is casting ‘evil’? Basically, because every time you do a type cast you are opening up your program to potentially unpredictable or unexpected behaviour. Let’s have a look at the four type-cast operators and the fun and games they can unleash on the unsuspecting.
static_cast<>
The static_cast operator converts between different object types; for example between a double and an int. So what you are effectively saying is
“I’m about to squeeze a big object into a smaller one, so you should probably make sure the receiving object is big enough to hold the values it’s going to get.”
Or:
“I’m about to force a floating point number into an integer and all those decimal places (that are probably quite important) are going to be lost”
Of course, you could also be saying:
“I’m about to put the contents of a small object into an object capable of holding much larger values (or with greater precision)”
(which is emphasising a bit of a non-problem, really)
Thankfully, C++ doesn’t let you type-cast between different class types unless you’ve defined your own explicit conversion functions (which – hopefully – should do a sensible conversion). But that’s for another time.
reinterpret_cast<>
reinterpret_cast is used in two ways:
- To convert a pointer-to-type into a pointer-to-different-type
- To convert an integer type to a pointer type; or vice versa.
When reinterpret_cast appears in code it tells the reader:
“I’m going to take the object address you gave me and treat it as a completely different type, with different memory layout and different behaviour(s). You should make sure it’s capable of supporting what I want to use it for.”
Or, in another usage:
“That (random) number you gave me? I’m going to use it as the address of an object. You’d probably better make sure it’s a valid address, in a reachable region of memory; unless you’re a big fan of segmentation faults.”
const_cast<>
The const_cast operator removes the const-ness of an object; that is, it makes a read-only object writeable.
Significantly for embedded programmers, const_cast removes any cv (const–volatile) qualification the original object may have. This means a volatile object – for example, one used to represent a hardware register in an embedded system – can have that qualification removed with const_cast.
Using const_cast says:
“The object you didn’t want me to change? I might (accidently) change it without your consent.”
Or, perhaps in an embedded system:
“The compiler might now optimise away any reads or writes to that object you gave me. Be prepared for behaviour NOT to happen as you expect!”
dynamic_cast<>
The dynamic_cast operator is a special case here, in that it is used for ‘safe down-casting’ – that is, casting a pointer-to-base-type to a pointer-to-derived-type, whilst checking whether this is, in fact, a valid cast. dynamic_cast uses Run-Time Type Identification (RTTI) to ensure the types of the pointers are valid. Thus, unlike the other cast operators, dynamic_cast is a run-time check and has associated overheads. If the pointer types are not compatible dynamic_cast returns 0 (for pointers) or throws an exception (for references).
dynamic_cast also has a role in multiple inheritance, where a class has two or more base classes. The dynamic_cast operator allows you to cast a pointer of one base class type to another. Although this is basically a variation on safe down-casting we tend to use the term ‘cross-casting’. Cross-casting is commonly encountered when a class realises (inherits from) two or more interface (pure virtual) classes.
In your code this means:
“I need to access the extended interface of a particular derived type. You’d better be prepared to deal with the consequences of the derived type NOT being what I want.”
Or:
“I need to know if the object you’ve supplied supports some other – possibly completely different – set of characteristics. ”
So – don’t use type casts?
Obviously, it’s impracticable (if not impossible) to write code with no type casting; especially in embedded systems. I leave you with the following guidance:
- Don’t cast if you don’t need to.
- Think about the consequences of what the cast is (potentially) doing.
- Leave a big, obvious comment documenting why we’re doing something so potentially dangerous.
- Practice makes perfect, part 3 – Idiomatic kata - February 27, 2020
- Practice makes perfect, part 2– foundation kata - February 13, 2020
- Practice makes perfect, part 1 – Code kata - January 30, 2020
Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry.
He specialises in C++, UML, software modelling, Systems Engineering and process development.
Thanks. This is the best explanation of the C++ type-cast operators that I have come across.