Hello again! In this video, we're going to look at casting. Casting means converting the type of
an expression.
The original form of casting, as used in C, was to convert an expression from one type to another.
When const came along, which was actually in C++ but imported back into C, there was a lot of code still
around that didn't use const.
So any new code that used const had to be able to work with old code, so they needed some way of converting
from const expressions to non-const expressions.
If you're working at lower levels - perhaps you might have a data structure which contain this data with
different types, and you want to send that data over a network which knows nothing about C++ types.
Then you have to convert your data into a stream of binary data, which is a string of ones and zeros.
So you need to cast that as well.
And then finally, when you're working with inheritance, which we'll be looking at later on in the course,
often you need to be able to convert a pointer to a base class into a pointer to a derived class.
And you may be thinking these all sound a bit dubious. And you're correct,
they are a bit dubious! If you're writing good C++ code.
It's very rare that you should actually need to put an explicit cast in your code.
If you do, it's often a sign that something suspicious is going on.
I mean, you might need it occasionally to interact with code written in C or some other code in C++
that's not been very well written.
But generally, it's a warning sign that something funny is going on.
The traditional way of casting is the way that it's done in C, which is to take the type that you once
you put in brackets before the expression.
So in this case, we're converting c to a character.
So we have this character, stored in an int. Presumably it's been read from input because that's
what the function call returns.
If we print out this character, which is actually an int, we get the numeric value.
But if we cast it to char, then we'll get the character.
So there we are. When we print out the int we get 65, when we cast to character, we get the letter A,
which is what we actually wanted.
So that's the kind of thing that people used to do in C.
When C++ was standardized, they decided to add some new cast operators, one for each type of cast.
And they also provided a very distinctive syntax.
So it stands out very clearly and there's no possibility of being in any doubt about what's happening.
So instead of having type in brackets before the expression, we have something which says what kind
of cast it is. Then the type is a template parameter,
so it goes in angle brackets and then he puts the expression that you want to cast in round brackets.
What's the reason for this syntax?
Bjarne Stroustrup said,
"An ugly operation should have an ugly syntactic form!" And he actually puts ugly in bold quotes, so
he obviously feels really, really strongly about this.
And he must have had some bad experiences. Which is not surprising, really, because casting turns off a lot of
compiler checks which prevent bugs.
So if you cast, you open yourself up to bugs that wouldn't normally occur.
So for the code that we had before, we would use static cast, which just means changing the static type
of the expression.
So we would change c to a char using a static cast.
So the code would look like this. We have this static_cast, we have character as the parameter and
then we have the expression we want to cast in brackets.
And then when we run this, we get the same result as before.
The difference is there's no chance you could miss this when you're scanning through code, even if
you're looking very quickly through hundreds or thousands of lines of code.
You'll notice this.
And also, it's very obvious what's happening.
It's definitely a static cast.
You don't have to stop and read the code to work out what it does.
The second form of cast is the const cast.
So this converts a const expression to the non-const equivalent.
So if you have something that's not supposed to be changed, this allows you to change it, which is
generally a bad thing to do.
You may need this occasionally to call old or badly-written code that doesn't use const when it
should, for example, in this function. We have a function that takes an argument.
The function doesn't actually change the arguments, but the argument is not const.
And then if we want to pass something that is const to this argument, we need to cast it to a non-const
variable, which is a bit dubious. But it's actually okay in this case because print doesn't change
the argument.
So we'd use the const_cast. We would have char * as the argument, because that's what we want to convert
the expression to. So it's originally a pointer to const char.
And then the argument is the thing we want to cast.
So this is going to take this expression, which is an array of const char or a pointer to const char,
and it's going to pretend that it's actually non-const car.
So it does work and print out
Hello, world.
And you may want to think about what happens if this function is changed and it does actually modify
the string that's passed into it.
What will happen is that you won't get a compiler error because you've told the compiler that you want
this to be
mutable.
So you've turned off an important compiler check. It may well be that this is stored in memory, which
is read only, because it's constant, so it's not going to be changed, of course.
And then when this function does change it, you may well get a memory violation.
So that's why that's a bad thing.
So if print does mutates the arguments, then you get undefined behavior.
And then very quickly looking at the other two, because they're a bit more advanced. reinterpret_cast
is the one that will convert data to untyped binary data.
And you'd mainly use that if you're doing low level work like communicating with hardware or the operating
system, or producing a binary file or reading in a binary file.
And we're actually going to look at how to work with binary files later on in the course.
(I'm giving you all these teasers!)
Then finally, dynamic_cast so this will convert a pointer to a base class object to a pointer to a derived
class object.
And it also works with references. And we'll talk about that later on when we go into polymorphism.
And this is different from the other types.
The other ones are all done by the compiler.
This is actually code that gets executed at runtime.
But again, we'll go into the details of that later on.
Okay, so that's all for this video.
I'll see you next time.
But meanwhile, keep coding!