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