Published Jul 23, 2019
[
 
]
C++, being a strongly typed language, is strict with its types. And there are always cases when you need to convert one type into another, which is known as casting.
int a = 10;
long b = a;
This is known as implicit conversion. More specifically, the above example is of standard conversion, which occurs automatically between fundamental types and few pointers.
There can also be implicit casts between classes with constructor or operator conversions.
struct A {};
struct B {
B (A a) {}
}
A a;
B b = a;
Here an implicit conversion happened from A
to B
because B
has a constructor that
takes an object of A as parameter.
static_cast
can be used to convert between pointers to related classes (up or down the
inheritance hierarchy). It can also perform implicit conversions.
class Mammal {};
class Human : public Mammal {};
Human* h = new Human;
Mammal* m = static_cast<Mammal *>(h); // cast it to pointer to base type. static_cast here is unnecessary
Mammal* m2 = static_cast<Human *>(m); // cast back to pointer to derived type
When we’re casting up the hierarchy, static_cast
is not needed. Every Human
is a
Mammal
. So a Human *
can be converted to a Mammal *
implicitly. static_cast
would
actually perform this implicit cast if you use it anyway.
But every mammal
may not be a Human
. So implicit conversion from Mammal *
to Human
*
is not allowed. That’s where static_cast
comes in. If we omit the static cast, we’ll
get an error something alone the line of
invalid conversion from 'Mammal*' to 'Human *'
But, using static_cast
, we can still do the conversion anyway. static_cast
doesn’t
perform any checks. So it’s the programmer duty to ensure that the conversion should be
valid. An invalid conversion might not fail, but can cause problems later when the pointer
points to an incomplete type and is dereferenced.
class Mammal {};
class Human : public Mammal {
public:
virtual void scream () { // note the virtual
cout << "MOM" << endl;
}
}
Mammal* m = new Mammal;
Human* h = static_cast<Human *>(m); // OK so far
h->scream(); // Mayhem!!
So don’t use static_cast
to cast down the hierarchy unless you’re absolutely sure the
conversion is valid.
static_cast
won’t let you convert between two unrelated classes
class A {};
class B {};
A* a = new A;
B* b = static_cast<A *>(a);
Gives an error -
cannot convert 'A*' to 'B*' in initialization
dynamic_cast
is related to static_cast
in the sense that it helps to cast through
inheritance, but it’s more powerful than static_cast
but has an overhead. It performs a
check in order to prevent the case above.
class Mammal {};
class Human : public Mammal {};
Human* h = new Human;
Mammal* m = dynamic_cast<Mammal*>(h); // ok
Human* h1 = dynamic_cast<Human*>(m); // error
The second conversion would produce a compilation error since base-to-derived conversion
are not allowed with dynamic_cast
unless the base class is polymorphic (has at least one
virtual
function, either declared or through inheritance).
class Mammal {
public:
virtual void scream(){}
};
class Human : public Mammal {
public:
void scream() override {
cout << "MOM" << endl;
}
}
Human* h = new Human;
Mammal* m = dynamic_cast<Mammal *>(h);
Human* h1 = dynamic_cast<Human *>(m);
h1->scream();
This works as you’d expect.
What happens if we try to cast a Mammal *
to a Human *
where the Mammal
is not
actually a Human
? (that crashed the static_cast
one)
Mammal* m = new Mammal;
Human* h = dynamic_cast<Human *>(m);
if(h == nullptr) {
cout << "Oops! Cast failed!" << endl; // this will be the result
} else {
h->scream();
}
So, as you can see, dynamic_cast
performs a check. It returns nullptr
if you’re trying
to convert pointers or throws std::bad_cast
if you’re trying to convert references.
But remember, this check happens in runtime, and not compile time. It require the Run-Time Type Information (RTTI) to keep track of dynamic types and thus has a slight overhead.
Let’s see now what happens when we try to convert two unrelated classes
class A {
public:
void f(){}
};
class B {};
A* a = new A;
B* b = dynamic_cast<A *>(a);
This doesn’t give a compilation error (unlike static_cast
, because the check is
performed at run time at that point b
will be nullptr
)
Finally, one more thing dynamic_cast
can do is “side cast.” To understand this, consider
this classic “dreaded diamond” hierarchy
struct A {
virtual void f() = 0;
}
struct L : A {
virtual void f() override {
cout << "Left" << endl;
}
}
struct R : A {
virtual void f() override {
cout << "Right" << endl;
}
}
struct D : L, R {
}
By side casting, we mean to say that we should be able to cast an object of type L
as
type R
and it should behave exactly as type R
(and vice versa). This is of course
possible only when the underlying object is actually of type D
. static_cast
however,
can’t help here.
D* d = new D;
L* l = d;
R* r= d;
A* bl = l;
A* br = r;
br->f(); // Right
static_cast<L *>(br) -> f(); // still prints "Right"
dynamic_cast<L *>(br) -> f(); // prints "Left"bl -> f(); // prints "Left";
static_cast<R *>(bl) -> f(); // still prints "Left"
dynamic_cast<R *>(bl) -> f(); // prints "Right"
const_cast
is the only cast that can be used to add const
to a type or take const
out of a type. This is useful when, say you want to pass a non const
argument to a
function which expects const
arguments.
void f(int& x) {
x = 5;
}
It expects a non const
reference to int
.
Now suppose you have something like this
int i = 4; // non const variable
const int& j = i; // const reference
If you try to call f
with j
as argument, you’ll get an error
error: binding reference of type 'int&' to 'const int' discards qualifiers
What you can do, is to remove the const
with const_cast
f(const_cast<int&>(j));
Note however, that you can remove const
away from an object only if it was actually
declared as non const
. Removing const
from a const
object is undefined behavior
const int i = 3;
int* p = const_cast<int*>(&i);
*p = 4; // undefined behavior
Similarly, you can also add const
to an object. const_cast
works on volatile keyword
too, although that’s rare.
This cast converts any type of pointer to any other type of pointer, even unrelated types. No checks are performed. It simply copies the binary data from one pointer to another.
All types of pointer conversions are allowed. You can even cast pointers to and from integer types. The only guarantee is that if you cast a pointer to an integer type that is large enough to hold it back, and then cast it back to pointers, you get a valid pointer
References: https://codeburst.io/understanding-c-casts-ef1f36e54240