24

Consider:

class base
{
    base();
    virtual void func();
}

class derived : public base
{
    derived();
    void func();
    void func_d();
    int a;
}


main
{
    base *b = new base();
    sizeof(*b); // Gives 4.
    derived * d = static_cast<derived*>(b);
    sizeof(*d); // Gives 8- means whole derived obj size..why?
    d->func_d();
}

In the above code I did downcasting of a base pointer which points to base object to derived class pointer. I am wondering how the derived pointer has the whole derived class object. I can call the derived class function (declared in derived class only). I did not get the concept here.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
G Mann
  • 441
  • 1
  • 4
  • 12

3 Answers3

27

Using static_cast to cast an object to a type it doesn't actually have yields undefined behavior. The symptoms of UB vary widely. There's nothing that says UB can't allow the derived member function to be called successfully (but there's nothing that guarantees that it will, so don't count on it).

Here is the rule for downcasting using static_cast, found in section 5.2.9 ([expr.static.cast]) of the C++ standard (C++0x wording):

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 a valid standard conversion from "pointer to D" to "pointer to B" exists, cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, and B is neither a virtual base class of D nor a base class of a virtual base class of D. The null pointer value is converted to the null pointer value of the destination type. 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 result of the cast is undefined.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 1
    I don't understand, irrespective of the type of cast, shouldn't this be not working at all? I mean, derived class may have methods and data members of which its parent class has no idea, so why isn't the downcasting throwing an error? – SexyBeast Jan 12 '15 at 07:52
  • 1
    @Cupidvogel: Because undefined behavior does not mean "guaranteed to throw an exception, or produce an error in any way". UB means there are **no guarantees at all**. – Ben Voigt Jan 12 '15 at 15:18
  • Oh okay, so yo are saying that even if we call a method declared in the derived class from the pointer to the base class (reinterpreted to derived class), there is no way compiler will detect this, and any exception will be caught at run-time? – SexyBeast Jan 12 '15 at 16:41
  • 1
    @Cupidvogel You probably know by now, but for a checked cast, you can use `dynamic_cast<>` which allows to detect the mismatch (either by throwing an exception for reference types or by returning a null pointer for pointer types). – Tilman Vogel Apr 02 '15 at 12:42
11

The only cast that does runtime checking is dynamic_cast<>(). If there is any possibility that a cast will not work at runtime then this cast should be used.

Thus casting from leaf->root (up casting) static_cast<>() works fine.
But casting from root->leaf (down casting) is dangerous and (in my opinion) should always be done with dynamic_cast<>() as there will be dependencies on run-time information. The cost is slight, but always worth paying for safety.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Martin York
  • 257,169
  • 86
  • 333
  • 562
  • 6
    It should be noted that `dynamic_cast` is safer than `static_cast` only if the base class defines virtual functions. – Ben Voigt Jun 12 '11 at 17:13
  • 3
    @Ben: If you are down-casting it is a compile time error if the type are not polymorphic (ie have virtual function). If you are up-casting then it is safe as it is the equivalent of a static_cast anyway. – Martin York Jun 12 '11 at 17:45
  • 1
    Which means that `dynamic_cast` can't globally replace `static_cast`, as your answer seems to suggest. (nor even all downcasts, consider the CRTP) – Ben Voigt Jun 12 '11 at 18:01
  • 2
    @Ben: Which means where it can't you get a compile time error. Which also suggests (but does not confirm) that your class should be polymorphic in the first place. I see no harm in a compile time error. Its a infinitely preferable to run-time UB. If you are down-casting there is probably something wrong with your design (or it is something that needs to be pointed out (and dynamic_cast makes it stick out)). Either way dynamic_cast will make the design safer; either a compile time error or a runtime-check that is easy to spot. – Martin York Jun 12 '11 at 18:19
  • Hi Crappy, how can downcasting be safe trough any cast at all? I mean, the derived class will have methods that the base class doesn't have, so if the user calls those methods through the pointer, how can `reinterpret_cast` save it? – SexyBeast Jan 12 '15 at 08:01
  • @Cupidvogel: dynamic_cast<> checks the runtime type. If the cast is valid it will work. If the cast does not work it returns NULL (on pointers) or throws (on references). So as long as you check the result of the cast after use it is safe. But in reality any cast is a design flaw in your code. `reinterpret_cast<>` can never save anything. Use `reinterpret_cast<>` to highlight in the code very dangerious casts that you want reviewers to take special care with. – Martin York Jan 12 '15 at 09:55
  • @LokiAstari You say that casts are design flaws, especially the need to downcast. But isn't the ability to treat members of a class hierarchy as their base class one of the prime benefits of polymorphism? Take, for example, a list of `Car*` to represent dealership stock, containing objects created as `Truck*` or `Sedan*`. What would you do differently if you needed to get a list of all the 'Truck's on hand and call `getBedLinerType()` for an inquiring customer? – John Neuhaus Feb 19 '16 at 15:37
  • I'm all for safety, but I'm happy to keep using `static_cast` from `Base` to `Derived` when my program's design means I absolutely know that my `Base` ptr/ref points to a `Derived`, in which case using a `dynamic_cast` would be a waste of runtime. – underscore_d Apr 13 '16 at 10:37
  • @underscore_d: Sure it works for you. But you are making your code very brittle in the long run. In any application of more than 10 lines that is used for more than a single use must be maintained, usually by people that are not going to know the full context of the application. If somebody changes the code in a way that you did not anticipate your are left with broken code; And how can you anticipate what a person you have never met will be thinking in 10 years when you have long left the company that owns the code! – Martin York Apr 18 '16 at 07:22
  • @LokiAstari Appreciate the assumption I'm coding on a pro level, but I'm not... yet? I guess that's the general assumption here, so I should've been more clear. I dabble in programming at work, but not application development. That's just a hobby. The project I referred to is so tightly knit that I don't feel a need to adhere to principles that make a lot of sense for shared code - rather ones that make it more efficient for me to understand. Could list more examples, but I might risk further jeers ;-) Were the code for a shared purpose then sure, I'd probably be more pessimistic in writing it – underscore_d Apr 18 '16 at 08:17
  • @underscore_d That's even more reason to follow the standard patterns not less. – Martin York Apr 18 '16 at 11:27
  • @LokiAstari As shown in the accepted answer, `static_cast`ing when you know it's valid is perfectly standard. I guess I just don't see any real point in spending (probably tiny and insignificant amounts of) time performing `dynamic_cast` when my project is carefully designed such that I _know_ the cast works and don't need a check. I'll take the risk that it'll confuse me briefly later, so long as it doesn't affect anyone else. Deal? – underscore_d Apr 18 '16 at 11:50
  • @underscore_d: That's not what the accepted answer says. So again this emphasis the point that you should be doing it the correct way rather than risking UB. Also how do you know it is correct. You have just remove your main tool for checking correctness. The role of the compiler is to tell you when you are doing stuff wrong. **BUT** a static_cast is specifically telling the compiler to shut up and don't warm me about this dangerous and potentially broken thing am doing. – Martin York Apr 18 '16 at 17:33
  • @LokiAstari The answer quotes a pattern of `B` and `D` that precisely match the beginning and end points of my downcast, so I do not need to involve RTTI. As the inheritance hierarchy in question is precisely defined by its authors and almost certain not to change (at least not without huge red flashing lights), what is wrong? Do you have anything other than theoretical objections about theoretical readers who wouldn't understand this (and wouldn't be willing to look through the related headers to verify it)? If not, I guess we'll have to agree to disagree on how much of a Sin I'm committing. – underscore_d Apr 18 '16 at 18:59
  • These are not theoretical issues these are actual real problems you are introducing into the code. The fact you think they are theoretical is the red flag. The fact you think static_cast is a solution is a problem. The fact that you think this is even a good idea is a problem. The fact that you want to spread bad advice is a problem. If you have a different point of view you should create your own answer to the question to see the votes it will get. Is your problem with doing it correctly a performance issue? If so they you probably have a design issue. If not then why not just do it correctly – Martin York Apr 18 '16 at 20:04
  • Given the quote from the Standard, why do you still think this is such a bad idea? It explicitly qualifies `static_cast` as allowed here. No, it's not really a performance issue, so much as I don't have to use `dynamic_cast`, so again, when the Standard says my usage is OK, why would I? Also, cut out accusing me of "wanting to spread bad advice", when I've simply made observations/questions; not once have I said 'other people should do this thing.' Honestly, all I really "want" at this point is a reasoned response from you about why this is _sooo, sooo bad_. But all I seem to get is preaching. – underscore_d Apr 18 '16 at 20:37
  • You not reading closely then: **BUT a static_cast is specifically telling the compiler to shut up and don't warm me about this dangerous and potentially broken thing I am doing** The compiler is there to stop you making mistakes. You are telling the compiler to shut up and ignore the dangerous stuff. When humans think they know better than the compiler then you are going to end up with broken code. – Martin York Apr 18 '16 at 21:06
  • Curious why you think you are better than the compiler and run time system (especially when we know humans are absolutely terrible at coding in general). Writing code that is brittle hard to diagnose when its wrong and even harder to fix (because UB is hard to find) is a bad habit. Why do you want to cultivate a bad habit when there is a perfectly good way of doing it that will prevent errors. Its like driving a car without a seatbelt. Sure you can do it but its a bad idea its a bad idea because its dangerous and you are not the one that has to clean up the mess. – Martin York Apr 18 '16 at 21:11
  • 1
    So yes it can be done. Yes it is an optimization for the experienced to you. Beginners should be safe and drive with their seatbelt on until they get the code reviewed by an expert that says yes this is an OK place to drive without your seatbelt because I (the expert) have vetted the situation for anomalies that you don't understand. – Martin York Apr 18 '16 at 21:14
  • As you mention before you are a beginner that only dabbles. You are explicitly the type of person that should be driving with your seatbelt on. Manly because you don't realize the box on your dash board is full of knives. Even I don't remember where all the knives in the car are (and get my code reviewed). You definitely don't know where all the knives are. – Martin York Apr 18 '16 at 21:16
  • @LokiAstari OK, the comment I upvoted is what I needed to understand your position: my situation isn't UB, just dangerous in your view and - I agree on the next - not a good default or something to advise to beginners (which I didn't do). Fwiw, I'm more of a determined hobbyist than a beginner ;-) To reiterate, (A) this is only my code, so I certainly would be "the one that has to clean up the mess", which I accept as penance if anything goes wrong (however unlikely); (B) if I were coding with others, I'd use the paranoid option, especially after your warnings. Thanks for expounding in detail – underscore_d Apr 19 '16 at 09:16
  • @underscore_d: Yet why ingrain bad habits? Why not just do it the correct way. Half the problems we programmers come across are bad habits that must be unlearned. – Martin York Apr 19 '16 at 16:33
  • "But casting from root->leaf (down casting) is dangerous." I wish this read "...is dangerous *if you aren't sure that it will succeed*". There are reasonable cases such as `if(myPtr->MyClassIdSystem() == MySubClassId) static_cast(myPtr);`. The answer gives the impression that static downcasts are inherently dangerous which may be incorrect depending on the surrounding code. – sleep Jul 12 '18 at 06:41
9

sizeof exists at compile-time. It neither knows nor cares that at run-time, your base object doesn't point to a derived. You are attempting to influence compile-time behaviour with a run-time variable, which is fundamentally impossible.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • Won't `sizeof` try to find the actual size of the object, that is, going through all the bits of the object and determine the size, rather than assuming the size of from the class of the associated object (this object belongs to Class D, which contains 2 ints, one float and one double, its size must be...). If it is the first, it should see that even though it is of type `D`, it doesn't have any associated space for method `func_d`, and thus shouldn't count its storage, because it is not there. – SexyBeast Jan 12 '15 at 08:00
  • 2
    @AttitudeMonger What was your question? All instances of a class share the same size. `sizeof` evaluates to a compile-time constant, not a runtime 'measurement'. Member functions do not occupy space in class instances. Virtual pointers might, and that's where `sizeof`-based adjustment comes in - which, again, doesn't stop you invoking UB. – underscore_d Apr 18 '16 at 11:14