C++ pointer multi-inheritance fun

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!


Solution 1:

  • 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.

Solution 2:

Item 28 Meaning of Pointer Comparison in C++ Common Knowledge: Essential Intermediate Programming) explains the key of object pointer in C++:

In C++, an object can have multiple, valid addresses, and pointer comparison is not a question about addresses. It's a question about object identity.

Take a look at the code:

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

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

class C derives from both class A and class B, so class C is both class A and class B. the object C c has 3 valid addresses: address for class A, class B and class C. The implementation depends on compiler, so you can't assume the memory layout of class C, and it may like this:

 ----------  <- pc (0x7ffe7d10e1e0)
 |        |
 ----------  <- pa (0x7ffe7d10e1e4)
 | A data |
 ----------  <- pb (0x7ffe7d10e1e8)
 | B data |
 ----------
 | C data |
 ----------

In above case, although the address value of pc, pa and pb aren't same, they all refer to the same object (c), so the compiler must ensure that pc compares equal to both pa and pb, i.e., pc == pa and pc == pb. The compiler accomplishes this comparison by adjusting the value of one of the pointers being compared by the appropriate offset. E.g.,

pc == pa

is translated to:

pc ? ((uintptr_t)pc + 4 == (uintptr_t)pa) : (pa == 0)

Among other things, since A and B have no inheritance relationship, we can't compare pa and pb directly.

For your questions:

(1) does pa point to a valid A object?  
(2) does pb point to a valid B object?  
Yes, refer the above diagram. 

(3) pc == (C*) pa ?  
(4) pc == (C*) pb ?  
Yes, No need to add (C*).

(5) does pa == pb ?
No. We can't compare them.

Solution 3:

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.

Solution 4:

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