0

I am learning C++ and in the tutorial, I came across a point that you can declare a pointer of type Class and assign address of any class object of same type - Quite normal.

I created a class cylinder which has two private double variables and one method to calculate volume.

// Create a class for cylinder
class Cylinder
{
    public:
    // Consturctor to set radius and height
    Cylinder(double r, double h)
    {
        radius = r;
        height = h;
    }
    //Calculate volute of Cylinder
    double volume()
    {
        return 3.14*radius*radius*height;
    }
    
    private:
        double radius{1};
        double height{1};
};

I added one more class Triangle which also has two double variables and one method to calculate area -

// Class to define traingle
class Triangle
{
    public:
    // constructor to set base and height
    Triangle(double b, double h)
    {
        base = b;
        height = h;
    }
    // Calculate area of triangle
    double area()
    {
        return 0.5*base*height;
    }
    
    private:
        double base{1};
        double height{1};
};

In main(), I created two objects - one of the each type (i.e. Cylinder and Triangle) and a pointer of Cylinder type. This pointer is pointing to Triangle object. Using Cylinder pointer (p_cyl), I am calling a method which is present in Cylinder class. I was expecting, either code to crash OR call method present in Triangle class. But to my surprise, pcyl called Cylinder's volume method with values of variables present in Triangle class object.

int main()
{
    // Create one cylinder and one triangle
    Cylinder cylinder1(4,5);
    Triangle triangle1(6,9);

    // Calculate volume and area
    cout<<"Volume is " << cylinder1.volume() << endl;
    cout<<"Area is " << triangle1.area() << endl;
    
    // Fun part: Create a pointer of class Cylinder and point it to triangle
    Cylinder *pcyl = (Cylinder*)&triangle1;
    // Verify if pcyl is really pointing to triangle1
    cout << "Address of Traingle" << &triangle1 << endl;
    cout << "pcyl is pointing to "<< pcyl << endl;
    
    // Now that pcyl is pointing to triangle, try to invoke method which is not part of the class.
    // Expecting a code crash or call "area" function from Triangle.
    cout<<"Cross-call result is " << pcyl->volume() << endl; 

    return 0;
}

I want to know why this behaviour? As pcyl is pointing to memory occupied by Triangle class object, how it is able to fetch Cylinder class's method.

(P.S. Yes, One should not do it in real life but this is to understand behaviour of Class pointers in C++)

EDIT: My concern is, how it works out in the memory? I understood that, Casting is something which is borrowed from C and what I am trying is undefined behaviour.

But, pcyl is pointing to a memory which doesn't have code to run instructions created for volume. The memory (as it is of Triangle class) have code for area. pcyl is able to fetch correct instructions of function volume even though these instructions are not present at the memory location it is pointing to!

Swanand
  • 4,027
  • 10
  • 41
  • 69
  • 2
    Quite normal?!? No it is not "normal" to circumvent C++s type system by C-style casts. A `Triangle` is not a `Cyclinder` and you cannot mix their pointers. I think you misunderstood something. "you can declare a pointer of type Class and assign address of any class object" is not correct – 463035818_is_not_an_ai Jun 14 '22 at 12:20
  • It can do that because that's what happens sometimes [when demons fly out of your nose](https://en.wiktionary.org/wiki/nasal_demon). When that happens you can't expect any particular result from a C++ program. – Sam Varshavchik Jun 14 '22 at 12:21
  • `(Cylinder*)&triangle1;` is called "c-style cast". This type of cast can easily hide bugs an errors. It basically tells the compiler: "Don't worry, I know it looks odd, but I know what I am doing". In almost all cases, if you cannot get it to compile without the c-style cast it is also not correct with the cast – 463035818_is_not_an_ai Jun 14 '22 at 12:22
  • @463035818_is_not_a_number I meant, Assigning address of the object of same type to pointer with same class type i.e. Assigning pointer of Cylinder object to Cylinder pointer. That indeed is quite normal! – Swanand Jun 14 '22 at 12:23
  • 1
    _"One should not do it in real life but this is to understand behaviour of Class pointers in C++"_ - the only thing you'll learn by doing stuff like this is how undefined behavior may or may not manifest itself depending on the weather. – Ted Lyngmo Jun 14 '22 at 12:23
  • 1
    wrong code does not necessarily crash or cause a compiler error. When your code has undefined behavior anything can happen. In the worst case it compiles without error or warnings and appears to do something – 463035818_is_not_an_ai Jun 14 '22 at 12:24
  • 1
    C++ is not a nanny language. It expects the programmer to provided code that is not ill-formed. And, it gives you enough rope to shoot yourself in the foot. Using a **cast** is one of the many shiny footguns that C++ provides. Powerful, and dangerous if used incorrectly. A cast tells the compiler "I know this looks wrong, but trust me, it's right", but if it is not right, then hijinks and hilarity ensue. – Eljay Jun 14 '22 at 12:24
  • Duplicate [Why in C++ a pointer of one class can be casted to a pointer of another class?](https://stackoverflow.com/questions/24566990/why-in-c-a-pointer-of-one-class-can-be-casted-to-a-pointer-of-another-class) – Jason Jun 14 '22 at 12:37
  • The reason why this happens to work even though it's undefined behaviour is the fact that the memory layout of `Cylinder` and `Triangle` are the same with the name of `Cylinder::radius` being replaced with `Triangle::base`. A pointer only stores the location of the memory, not type info; The compiler gets the type info from the pointer type, so calling `pcyl->volume()` reads `radius` from what's actually the `base` data of `Triangle`. Of course you shouldn't rely on this working as described, since as mentioned its undefined behaviour... – fabian Jun 14 '22 at 12:38
  • @fabian Thank you for understanding the question :-) Yes, It gets the variables from `Triangle` and "maps" them to `Cylinder`. But how it is able to fetch the code of `Cylinder` class when it is pointing to `Triangle`? – Swanand Jun 14 '22 at 12:40
  • @Swanand As mentioned the pointer only contains information about the storage of the data. The implementation of the member functions only exists once per program and is stored somewhere else. All the pointers effectively indicate is the location of the data `struct TriangleData { double base; double height; };` and `struct CylinderData { double radius; double height; };` The compiler uses the type of `pcyl` to determine which function to call. You can think of member functions as effectively being free functions receiving `this` as hidden param `double volume(Cylinder* this);` – fabian Jun 14 '22 at 12:47
  • @fabian Thanks...That makes sense. When a class object is created, memory is allocated only for data and all methods are located at fixed locations. When a pointer to object is created, it points to data from the object but for methods, it fetches from fixed location and not from object. If you can post this as an answer with maybe a proof by printing addresses of the functions, I will accept it as answer (as I am novice in C++ and not able to print addresses of functions inside class. I tried and failed). – Swanand Jun 14 '22 at 12:53

2 Answers2

-1

In C++, (address of) non virtual functions of a class are not stored in the object of the class. They are statically linked at compile time. Since you are using a pointer of type Cylinder to make function calls, code is generated to call those functions of Cylinder in compile time. But the data contents of the Triangle object is passed, which will lead to unexpected behavior as expected.

-2

I want to know why this behaviour? how it is able to fetch Cylinder class's method.

The program has undefined behavior meaning the program is still in error even if it doesn't say so explicitly and "seems to be working".

Undefined behavior means anything1 can happen including but not limited to the program giving your expected output. But never rely(or make conclusions based) on the output of a program that has undefined behavior. The program may just crash.

So the output that you're seeing(maybe seeing) is a result of undefined behavior. And as i said don't rely on the output of a program that has UB. The program may just crash.

So the first step to make the program correct would be to remove UB. Then and only then you can start reasoning about the output of the program.


1For a more technically accurate definition of undefined behavior see this, where it is mentioned that: there are no restrictions on the behavior of the program.

Jason
  • 36,170
  • 5
  • 26
  • 60