views:

1164

answers:

6

Consider the following piece of code :-

class A {};

class B : private A {};

B* bPtr1 = new B;
// A* aPtr1 = bPtr1; // error
// A* aPtr2 = static_cast<A*>(bPtr1); // error
A* aPtr3 = (A*)bPtr1;
B* bPtr2 = (B*)aPtr3;

The C-style cast discards the private inheritance while both the implicit and static_cast fail (also dynamic_cast). Why ? If C-style casts are just bit-fiddling, how are C++ casts implemented i.e. how do they know the type of inheritance from memory footprint?

After bPtr1 is casted to aPtr3, i will have to use another C-style cast to downcast to B as again both static_cast and dynamic_cast fail. So, is bPtr2 guaranteed to be good?

Thanks in advance

+6  A: 

The C-style cast effectively does a reinterpret_cast. Once you use reinterpret_cast, you're totally on your own; it's basically undefined behaviour, and (to answer your question) there are no guarantees. (But, according to 5.2.10.7, you can always safely reinterpret_cast back to the original type, as long as the type you converted to does not have stricter alignment requirements than the original type.)

In practice, it's especially dangerous to use reinterpret_cast (or anything other than dynamic_cast) when you have multiple inheritance involved.

Chris Jester-Young
If I remember right, you can safely reinterpret_cast to void* and back, but let me read up on the C++ standard and check.
Chris Jester-Young
Abhay
It knows because your header file (or wherever your class was defined) said so. By the way, compiling one file using one definition of a class (e.g., with public inheritance), then linking that with another file compiled using a different definition (e.g., with private inheritance), has undefined behaviour too. So don't try to edit your header file unless you're prepared to recompile all files that use that class.
Chris Jester-Young
In other words, the compiler does not read class definitions from your object (.o or .obj) files, but rather from the header files. It is up to you to ensure that the header file matches what's in the object files.
Chris Jester-Young
Thanks for your precise answers
Abhay
A: 

The C++ casts are enforced by the compiler (not the linker). It's not that private inheritance causes a different class layout; it's that the compiler will forbid you from casting a pointer to a derived class to a pointer to its base class if the inheritance is not public, based on the declaration of the class.

rlbond
A: 

If C-style casts are just bit-fiddling, how are C++ casts implemented i.e. how do they know the type of inheritance from memory footprint?

C-style casts in C++ mean more than meets the eye. They are technically the only way to ask the compiler to choose the right cast operator required for conversion. For the same reason, this is better than using reinterpret_cast. The latter is implementation defined.

It seems there is a lot of confusion around C-style casts. Remember, no tool is safe until you know how to use it. The same applies to the C-style cast. The point of having a C-style cast is to ask the compiler to choose the safest and most portable conversion for a given pair of types. This may trigger a change from static_cast to reinterpret_cast silently and introduce errors. The point is: you need to know what you are doing.

dirkgently
Thanks for letting me know the 'implementation-defined' aspect of the reinterpret_cast.
Abhay
@dirkgently: Yes, C-style casts ask the compiler to choose the right cast for you, but this makes them **worse** than reinterpret_cast -- at least with reinterpret_cast, you are making your intention to do something "naughty" obvious (greppable in fact), thereby easing maintenance. A C-style cast is allowed to, and will often, perform a reinterpret_cast under the hood **without telling you**. -1.
j_random_hacker
Casts are dicey at best. So, I'd rather have the compiler than choose something worse. I would rather use a C-style cast than reinterpret_cast.
dirkgently
OK, I think a terrible analogy is in order :) If someone sent you a bomb in the mail, would you rather it looked like a bomb, or looked like any other mail? reinterpret_cast is a bomb that looks like a bomb, a C-style cast is a normal-looking piece of mail that might contain a bomb.
j_random_hacker
I don't think that's how I would read the code. I'd read it: Okay, here's a bomb, and I've called in the experts. But beyond that, all bets are off.
dirkgently
-1 for using C-style casts, which will silently cast away constness.
Thomas L Holaday
@Thomas L Holaday: C-style casting can be used with consts. However, it seems the whole point of this post is lost on most.
dirkgently
Abhay
@dirkgently - it sounds like you are encouraging ignorance ("I have a bomb... all bets are off"). Casts (C or C++) do have well-defined behavior, and no bets should be off. A developer using this part of the language should know *exactly* what it is doing, or step away from the keyboard.
Tom
I've discovered (embarrassingly) that there are some things that can *only* be accomplished using C-style casts, and converting from a pointer-to-derived to a pointer-to-INACCESSIBLE-base is one of them, so my suggestion to use reinterpret_cast instead can't actually be used in this particular case. But dirkgently, I still think your reasoning is suspect -- when you do something "tricky", surely it's best to draw attention to it if possible. (Or do you think a C-style cast already draws enough attention?)
j_random_hacker
@Tom: You are quoting me out of context.@j_random_hacker: To me any cast draws a frown.
dirkgently
@Tom: I am specifically trying to point out where you can rely on C-style casts. I am not trying to encourage ignorance, rather, on knowing what semantics you unleash with a particular syntax and using it effectively. I rest my case here.
dirkgently
+3  A: 

The existing answers are great, but one bit of information you may find helpful relates to this:

C-style casts are just bit-fiddling

If you use an old-style cast between two pointer types, the compiler usually wouldn't need to fiddle with any bits. You're telling the compiler that you want to treat a location of memory as if it contained some type (because you know that it effectively does), so the compiler does nothing at all at runtime to modify the contents of memory. Casting merely disables type safety at compile time.

Daniel Earwicker
A: 

Its been a few months but lets see if i can remember static_cast tries to do a conversion from right type to left (address of left side may not be the same as right side). Since it doesnt know if A* came from B* it fails.

With dynamic cast what i assume it checks the vtable on the right side (thus only class with virtual functions work) to see if its compatible with the left side. If not it returns null.

CStyle cast are basically reinterpret_cast. You have no safety and if you mess up you'll get bitten.

since your using CStyled/reinterpret nothing is guaranteed so bPtr2 is not guaranteed to be good although this code should compile and run properly since it doesnt look like you mess up or depend on anything compiler or platform specific.

If you want complete safety you can add a virtual function call getType or safeCast(). IIRC you may only need to do this once in every derived base (i could be wrong) and it only will add 32 or 64bits to your vtable and vtables are only stored once (then every obj has a pointer to its vtable). So. thats pretty much a non existent performance penalty and you cant implement a more efficient safety check in code so you should use it if you want one. You can then use dynamic cast to upcast an object.

acidzombie24
+2  A: 

EDIT: Following a comment by dribeas, I did some further research and it turns out that my main conclusion was incorrect. It turns out that the standard states in 5.4.7 that C-style casts can actually do more than any sequence of new-style casts can do -- specifically including casting from a pointer-to-derived to pointer-to-base even when the base class is inaccessible, which is precisely what happens here with private inheritance. (Why this should be allowed, and in particular why it should be allowed only for C-style casts, is utterly beyond me; but it's undeniably allowed.)

So dribeas is right, compilers are obliged to handle the OP's C-style pointer conversion correctly, even when B inherits from multiple base classes. My own testing with MSVC++8 and MinGW confirms his results in practice -- when B inherits from multiple base classes, the compiler will adjust pointers when converting a B* to an A* or vice versa so that the correct object or subobject is identified.

I stand by my assertion that you ought to derive B publicly from A if you ever intend to treat a B as an A, since using private inheritance instead necessitates using C-style casts.

The original (somewhat incorrect) answer appears below.


Ultimately, if you want to be able to treat a B as an A, you need to derive B publicly from A. Using C-style casts may work sometimes on some architectures and with some compiler versions, but it's simply an unportable hack that's liable to break at any time (e.g. when turning on compiler optimisations).

As an example of a case that will definitely break on all compilers I know of, if B was actually specified as follows:

class B : public X, private A { ... };    // X is any other class

Then the following code:

A* aPtr3 = (A*)bPtr1;

Will point aPtr3 at the wrong subobject of bPtr1, leading to crashes as soon as a member is accessed or a virtual method is called. (The C-style cast will be converted to a reinterpret_cast because no valid conversion using static_cast or dynamic_cast is available, meaning that aPtr3 will be assigned the same memory address held in bPtr1 -- but because multiple inheritance is in effect, the A subobject contained in *bPtr1 is not located at the same memory adress as bPtr1.)

The moral of the story is that deriving a class privately from another class does not specify an is-a relationship. It's probably best not to think of it as inheritance at all.

j_random_hacker
I have just tested it with gcc 4.0 on MacOSX and the code does not break. Calling a virtual method defined in A does actually call the B version and the program does not die. If there is an intermediate conversion to void* then it erroneously calls the B version of a virtual B method (the first method in the vtable in my test). This does not mean that it is not undefined though (I don't know).I agree that C style casts should be avoided and that the C++ versions are more cumbersome so that all alarms go off, but I cannot upvote this answer for the previous reason.
David Rodríguez - dribeas
@dribeas: Intriguing, I'm gonna do some tests myself in a moment. One thing -- was your X by any chance empty of data elements? In that particular case, the Empty Base Optimisation would kick in, allowing the A subobject to live at the same address as the B object. But if X contains data members, I believe the A subobject cannot exist at the same address, leading to breakage. (In any case, that wrinkle should have been mentioned in my post.)
j_random_hacker
Well, time to eat some humble pie... It turns out the standard states that C-style casts can do *more than any sequence of new-style casts can do* in 5.4.7 -- specifically including casting from a pointer-to-derived to pointer-to-base even when the base class is inaccessible, which is precisely what happens here with private inheritance. So dribeas is right, compilers are obliged to perform the necessary pointer adjustments so that the code I posted will work. My own testing with MSVC++8 and MinGW confirms his testing.
j_random_hacker