-2

I was playing around with pointer to the base class, and I casted a rectangle pointer into a circle pointer and called the printradius() function from the rectangle! Could someone explain why this is allowed? Thanks.

#include <iostream>
#include <string>
using namespace std;

class Shape {
};

class Circle: public Shape {
    private:
        double radius;
    public:
        Circle(double r)
        { radius = r;}
    void printradius()
        { cout << "circle's radius is " << radius << endl;}
};

class Rectangle: public Shape {
    private:
        double width, length;
    public:
        Rectangle(double l, double w)
        { length = l; width = w;}
};

int main() {

    Rectangle r( 2.0, 2.0);   // only a rectangle is created
    Shape* s = &r;            // up cast into a shape
    Circle* c = static_cast<Circle*>(s); //down cast into a circle
    c->printradius();

}

output:

circle's radius is 2

Ramya Rao
  • 86
  • 7
tao lin
  • 7
  • 1
  • 2
    It's simple undefined behaviour. – Kerrek SB Jul 30 '15 at 19:21
  • As an aside you should use the initializer syntax with your constructors `Rectangle(double l, double w):length(l),width(w){}`. As for your question it's probably because the memory offset from the start of `Circle` class happens to be the same as the memory offset of one of width or length in the `Rectangle` class. So when `print_radius` is called a lookup to a certain offset is made that *coincidentally* looks up a valid double. You shouldn't rely on this though as it's undefined behaviour. Say the memory layout changes or another implementation does something else this will break. – shuttle87 Jul 30 '15 at 19:23

2 Answers2

3

What do you mean by "allowed"?

The language explicitly states that the result of such static_cast is undefined, so it is not really "allowed" in this case. In order to perform a valid downcast from Shape * to Circle *, you have to ensure that Shape * actually points to Circle or something derived from Circle. Otherwise, the behavior is undefined.

As for why the compiler did not catch it... The compiler cannot possibly catch errors that depend on run-time conditions. In general case the compiler does not know what your s pointer actually points to.

As for why the language even offers such feature... It offers it because it can be extremely useful when one uses it properly.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • "the result of such `static_cast` is undefined" -- NOT TRUE! When dealing with standard-layout classes (these are), **you are permitted to inspect a common initial sub-sequence of non-static data members**. The undefined behavior enters in when a member function is invoked on a mismatched object pointer, because member function calls don't fall within the special permission. – Ben Voigt Aug 03 '15 at 13:40
  • @Ben Voigt: Is it something that was changed in the final version of C++14? Since the draft I'm looking at (n3690) is as restrictive as it has always been in this respect: **5.2.9 Static cast** "11 A prvalue of type “pointer to cv1 B,” where B is a class type, can be converted to a prvalue of type “pointer to cv2 D,” where D is a class derived from B [...] If the prvalue of type “pointer to cv1 B” points to a B that is actually a subobject of an object of type D, the resulting pointer points to the enclosing object of type D. Otherwise, the behavior is undefined." – AnT stands with Russia Aug 03 '15 at 14:46
  • Hmm, the actual rules I can find for common initial sequences only mention unions.... but I see this done using pointers all the time. Possibly in order for it to officially be legal, one should cast through the type of the first member. Oh, I see, this usage needs `reinterpret_cast` (which explicitly mentions standard-layout classes), not `static_cast`. – Ben Voigt Aug 03 '15 at 15:16
0

The static cast is "because I said so.", in other words, trust me, the programmer, that the thing is what I say it is, a circle pointer in this case. When calling the printradius() method, The this pointer is for r, and when printradius() happens to deref looking for a radius, it finds that first double, with a value of 2.0. There is no crash, or reported error from running this way, but as you know, it makes no sense.

Use a dynamic cast and check the value returned. You will see null, because rectangle and circle are not the same.

donjuedo
  • 2,475
  • 18
  • 28
  • These types aren't *polymorphic*, so you **can't** use `dynamic_cast` – Ben Voigt Jul 30 '15 at 23:10
  • That's right. I did point out that he should expect null. "Polymorphic" is the nice was of saying "rectangle and circle are not the same". :-D – donjuedo Jul 30 '15 at 23:24
  • It won't produce null, because it won't compile – Ben Voigt Jul 30 '15 at 23:32
  • @BenVoigt, Indeed, you are right. I have learned something. My experience has always been with polymorphic classes, and compiling worked fine. But in this case, even though both classes inherit from the same base, the compiler (and you) caught that they are too different, and it won't compile. I stand corrected. – donjuedo Aug 03 '15 at 13:29
  • It's not a matter of "too different". There are no `virtual` member functions here, and `dynamic_cast` uses the virtual function metadata (usually address of a table of virtual member functions that is stored as a pointer in the object) in order to determine the dynamic type of the object and whether the cast is possible. No virtual functions -- not allowed to `dynamic_cast`. (Pedantically -- the Standard doesn't require comparing vtbl pointers, but the presence of virtual functions is what relaxes the layout rules and allows the compiler to add whatever extra information is needed) – Ben Voigt Aug 03 '15 at 13:36