#include <iostream>
using namespace std;
class Mango {
public:
Mango() {
cout<<"mango called"<<endl;
}
void print() {
cout<<"mango"<<endl;
}
};
class Apple {
public:
Apple() {
cout<<"apple called"<<endl;
}
void print() {
cout<<"apple"<<endl;
}
};
int main(int argc, char* argv[]) {
Mango* m = new Mango();
Apple* a = reinterpret_cast<Apple*>(m);
a->print();
return 0;
}
The output is
mango called
apple
I can see that a->print()
is printing the contents of apple
object, but apple
constructor was never called, so apple
object was never created.
How is this even working? How is the pointer a
pointing to apple
object which was never created? Shouldn't it still point to the bits which have the data generated by Mango
object?
It's technically undefined behavior. But here's a reasonable explanation about why it happens to work with non-virtual methods.
When the compiler generates code, it more or less converts C++ methods to have an implicit "this" argument passed to each.
Imagine you had a Foo
class with a method such as:
void Foo::doSomething(int x, int y) {
z = x*y;
}
Under the hood, the compiler will generate it similar to a C function to something logically (but not exactly) named this:
void Foo_doSomething(Foo* this, int x, int y) {
this->z = x*y;
}
Back to the mango and apple example. The print method from both classes are more or less generated like this:
void Mango_print(Mango* this) {
cout<<"mango"<<endl;
}
void Apple_print(Apple* this) {
cout<<"apple"<<endl;
}
This line of code:
a->print();
Is more or less compiled to invoke the print function like this:
Apple_print(&a);
Because neither function actually access the "this" parameter passed in, it just happens to work ok.
If you extend both classes like the following to have a "z" variable
class Mango {
public:
double z;
Mango() {
cout<<"mango called"<<endl;
z = 3.14;
}
void print() {
cout<<"mango: "<< z << endl;
}
};
class Apple {
public:
int z;
Apple() {
cout<<"apple called"<<endl;
z = 42;
}
void print() {
cout<<"apple: " << z << endl;
}
};
You'll get quite unexpected results when casting a Mango to an Apple and vice-versa. It won't print 42
or 3.14
on a casted object.
Ironically, Objective-C (which used to be popular on Mac) does allow for some of these types of casting shenanigans for invoking code on classes with similar function signatures. A side effect of "message passing", but I digress.