I'm trying to understand how casting between base & derived types exactly works in C++. So I wrote a small proof-of-concept program
class Foo {
public:
Foo() {}
};
// Bar is a subclass of Foo
class Bar : public Foo {
public:
Bar() : Foo() {}
void bar() { std::cout << "bar" << std::endl; }
void bar2() { std::cout << "bar with " << i << std::endl; }
private:
int i = 0;
};
where Foo is the base and Bar is derived from Foo.
Currently, my understandings of casting are:
Foo f = Bar()
), either explicit or implicit, should be always fineBar b = Foo()
) is prohibited in C++, although we can enforce the cast by using static_cast
I write 3 different programs to verify my understandings. Each program is compiled using
g++ -std=c++17 -Wall -Wextra -pedantic
int main() {
Foo f;
Bar &b = static_cast<Bar &>(f);
return 0;
}
Code compiles successfully. Running is program will not result in any error
My thoughts: ok, although the actual casting is not right as we are treating a instance of Foo as Bar at runtime, we are not seeing any error because we don't really operate on b
int main() {
Foo f;
Bar &b = static_cast<Bar &>(f);
b.bar();
return 0;
}
Code compiles successfully. Running this program will not result in any error, and "bar" is printed
I start to be confused here: why this program ever works and "bar" gets printed? Although here we are treating a Foo as Bar, the underlying instance is still a Foo and it has no method named "bar" defined on it. How could this code works?
int main() {
Foo f;
Bar &b = static_cast<Bar &>(f);
b.bar2();
return 0;
}
Code compiles successfully. Running this program will not result in any error, and "bar with 1981882368" (some random number) is printed
I'm even more confused here: if we think in terms of memory layout, the underlying Foo instance has no space reserved for member i
which is defined in Bar. How could this code still works?
Please help me understand the programs above! Thanks in advance!
Cast is a runtime thing. Compiler can do us a favor by checking them during compilation, but the actual type conversion occurs during runtime
No, with exception of dynamic_cast
, all casts are pure compile-time constructs. After all, there are (almost) no types at runtime in C++.
Upcast (e.g.
Foo f = Bar()
), either explicit or implicit, should be always fine
Yes, upcasts are safe.
Downcast (e.g.
Bar b = Foo()
) is prohibited in C++, although we can enforce the cast by usingstatic_cast
No, it is not prohibited, there are just some non-trivial rules. Some casts/conversions are implicit, some must be requested explicitly.
Case 1 : This is undefined behaviour(UB) because b
does not point to a real Bar
object. This cast assumes, the user know what they are doing, perhaps because they have some external information about the true type of the object, although not the case here.
Case 2 : You have triggered the UB, anything can happen. In this case, the compiler likely just called bar
and passed b
as this
pointer. That is of course incorrect but that is your problem. Since the method does not use this
pointer, there is not much to break.
Case 3 : Well, now you are really digging into this UB, the compiler likely just calculated this+offsetof(Bar,i)
and assumed the address points to an integer. The fact that it does not is your problem for breaking the promise of downcasting to the correct type.