views:

211

answers:

4

I'm writing some code involving inheritance from a basic ref-counting pointer class; and some intricacies of C++ popped up. I've reduced it as follows:

Suppose I have:

class A{};
class B{};
class C: public A, public B {};

C c;
C* pc = &c;
B* pb = &c;
A* pa = &c;

// does pa point to a valid A object?
// does pb point to a valid B object?

// does pa == pb ?

Furthermore, does:

// pc == (C*) pa ?
// pc == (C*) pb ?

Thanks!

+3  A: 
  • does pa point to a valid A object?
  • does pb point to a valid B object?

Yes, the C* gets converted so that pa and pb point to the correct addresses.

  • does pa == pb ?

No, usually not. There can't be an A object and a B object at the same address.

Furthermore, does

  • pc == (C*) pa ?
  • pc == (C*) pb ?

The cast converts the pointers back to the address of the C object, so both equalities are true.

sth
`dynamic_cast` is inappropriate here and C-style is correct (albeit bad form). According to §5.4/7, C-style casting may invoke a `static_cast` where "— a pointer to an object of non-virtual base class type, an lvalue of non-virtual base class type, or a pointerto member of non-virtual base class type may be explicitly converted to a pointer, a reference, or a pointer to member of a derived class type, respectively."
Potatoswatter
Yeah, you are right, the dynamic cast would only be needed if the `pa` would be casted to a `B*`, for example.
sth
In that case, the `dynamic_cast` expression would just be a fancy way to say `NULL`. An A* can't be a B* so it would always fail.
Potatoswatter
@Potatoswatter: No, that would work, since `pa` really is a `C*`, and a `C*` can be converted to a `B*`. But to figure that out a `dynamic_cast` is needed.
sth
@sth: If `pa` is a `C*` but you know it points to an `A`, then you can and should use `static_cast`. `static_cast` and `dynamic_cast` solve different problems; `dynamic_cast` is irrelevant here and unusable unless he adds virtual destructors.
Potatoswatter
`does pa == pb` In this question the answer is yes. The standard does state that complete objects must have a unique address, pointers to base classes can have the same value. In fact, since pointers are just addresses, you can have many pointers of various types point at the same address.
Skizz
@Skizz: Assume by default that the base classes aren't empty. A newbie doesn't need to be misled by a technicality about his example. `pa == pb` is UNDEFINED, the answer isn't a definite yes anyway.
Potatoswatter
@Potatoswatter: regarding your '@sth: if pa is a C* but...' comment. I think you have it backward here. `pa` is of type `A*` but points to an object of dynamic type `C` which inherits from both `A` and `B`. Since going to `B*` involves first passing through `C*` it requires a dynamic cast to ensure that `pa` does not point to a straight `A` object.
Matthieu M.
@Matthieu: You're right, I got A and C mixed up in that comment. You can certainly cast between pointers to base types using RTTI. Doesn't relate to the OP though, and you need a virtual destructor. And, if `pa` is an `A*` but you know it points to a `C`, you can still get a `B*` with *two* `static_cast`s and no vtable.
Potatoswatter
@Potatoswatter: sure enough, I tend to favor `dynamic_cast` over `static_cast` for upcasting though, just to avoid a stupid mistake :x
Matthieu M.
+1  A: 
pc == pa;
pc == pb;

Not defined, depends on class structure.

pc == (C*) pa;
pc == (C*) pb;

Thats ok.

pa == pb;

No.

Do they point to valid objects?

Yes
George B.
+1  A: 

C embeds an A and a B.

class C: public A, public B {};

is very similar to the C code

struct C {
    A self_a;
    B self_b;
};

and (B*) &c; is equivalent to static_cast< B* >( &c ) is similar to &c.self_b if you were using straight C.

In general, you can't rely on pointers to different types being interchangeable or comparable.

Potatoswatter
A: 

What you get is something like this in memory

 ----------
 | A data |
 ----------
 | B data |
 ----------
 | C data |
 ----------

So if you want the entire C object you'll get a pointer to the beginning of the memory. If you want only the A "part", you get the same address since that's where the data members are located. If you want the B "part" you get the beginning + sizeof(A) + sizeof(whatever the compiler adds for vtable). Thus, in the example, pc != pb (could be pc != pa) but pa is never equal to pb.

Francis Boivin
I beg to differ on the `pc != pb`. Typing the question's code into DevStudio 2005, I get all pointers with the same value. And that's probably what the C++ standard says should happen, in this case - no virtual methods and no data members. The OP has posted another question where the base classes do have members, so that has a different answer, which is this answer more or less.
Skizz
@Skizz: what you're seeing is called the Empty Base Class optimization http://www.google.com/search?q=empty+base+class+optimization it's implemented by all modern compilers but is far from being required.
Potatoswatter
class A{};class B{};class C: public A, public B{};int main(){ C c; C* pc( A* pa(pc); B* pb(pc); printf("C=0x%08X\nB=0x%08X\nA=0x%08X\n", pc, pb, pa);}output from VS2008 Release buildC=0x0018FEFFB=0x0018FF00A=0x0018FEFFIf you change the order in which C inherits from A and B, you'll get different values for A and B
Francis Boivin