Search code examples
c++pointersreinterpret-cast

How is reinterpret_cast casting pointer to a different type without creating the object?


#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?


Solution

  • 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.