18

I have a class Derived that inherits directly from two base classes, Base1 and Base2. I'd like to know if it's safe, in general, to compare pointers to the base classes to determine if they are the same Derived object:

Base1* p1;
Base2* p2;

/*
 * Stuff happens here. p1 and p2 now point to valid objects of either their
 * base type or Derived
 */

//assert(p1 == p2); //This is illegal
assert(p1 == static_cast<Base1*>(p2)); //Is this ok?
assert(static_cast<Derived*>(p1) == static_cast<Derived*>(p2)); //How about this?

The pointers are guaranteed to be valid, but not necessarily to point to a Derived object. My guess is that this is probably fine, but I wanted to know if it was ok from a technical C++ perspective. I actually never do any operations on the pointers, I just want to know if they point to the same object.

EDIT: It seems to be safe if I can guarantee that p1 and p2 point to Derrived objects. I basically want to know if it is safe if they don't- if one or both point to a base object, will the comparison necessarily fail? Again, I can guarantee the pointers are valid (i.e., p1 would never point at a Base2 object or vice versa)

4
  • I was under the impression that c++ did not support multiple inheritance. do you mean "Base1->Base2->Derived" Commented Jun 28, 2012 at 21:30
  • 12
    C++ does indeed support multiple inheritance :) Commented Jun 28, 2012 at 21:32
  • It's one of the easiest ways to shoot yourself in the foot, but yes, C++ supports MI Commented Jun 28, 2012 at 21:33
  • 2
    "It's one of the easiest ways to shoot yourself in the foot" No. Commented Jul 26, 2012 at 21:09

7 Answers 7

7

Well, no, it won't work.

I'm personally a big fan of learning-by-example, so here's one:

#include <iostream>

class Base1
{
public:
    Base1()
    {
        numberBase1 = 1;
    }

    int numberBase1;
};

class Base2
{
public:
    Base2()
    {
        numberBase2 = 2;
    }

    int numberBase2;
};

class Derived : public Base1, public Base2
{
public:
    Derived()
    {
        numberDerived = 3;
    }

    int numberDerived;
};

int main()
{
    Derived d;
    Base1 *b1 = &d;
    Base2 *b2 = &d;

    std::cout << "d: " << &d << ", b1: " << b1 << ", b2: " << b2 << ", d.numberDerived: " << &(d.numberDerived) << std::endl;

    return 0;
}

One run-through on my computer outputted this:

d: 0035F9FC, b1: 0035F9FC, b2: 0035FA00, d.numberDerived: 0035FA04

Soo.. If we define the address of d as 0, then b1 is 0, b2 is +4 and the number of d is +8. This is because an int on my machine is 4 byte long.

Basically, you have to look at the layout of how C++ internally represents a class:

Address:    Class:
0           Base1
4           Base2
8           Derived

.. So in total, instantiating a Derived class will allocate space for the base classes of the derived class, and finally make room for the derived object itself. Since we have 3 integers here, that'll be 12 bytes.

Now, what you're asking (unless I misunderstood something) is if you can compare the address of the different base class pointers to each other to see if they point to the same object, and the answer is no - Not directly at least, as in my example, b1 would point to 0035F9FC, while b2 would point to 0035FA00. In C++, this offsetting is all done at compile time.

You could probably do some magic with RIIA and sizeof() and determine how much of an offset b2 should have to be comparable to b1, but then you run into all kinds of other trouble like virtuals. In short, I would not recommend this approach.

A much better way would be to cast to Derived* like ialiashkevich said, however, that would impose a problem if your object was not an instance of Derived*.

(Disclaimer; I haven't used C++ in 3-4 years, so I might be a bit off my game. Be gentle :) )

Sign up to request clarification or add additional context in comments.

1 Comment

Dynamic casting to void* is the right (as in "robust") way to go. As in @Eran 's answer.
6

Casting to Derived* before comparison is the right way to go.

There is a similar topic: C++ pointer multi-inheritance fun

4 Comments

"pointers are guaranteed to be valid, but not necessarily to point to a Derived object". This is not to say your suggestion won't work, but you must use dynamic_cast, and you must handle the case they both return NULL (which might or might not mean they point on the same object).
Oh they'll never be null- I thought I mentioned that. They're guaranteed to point at a valid object of their type.
@Lucretiel, if you call dynamic_cast<Derived*>(p) and p points to a valid object of type Base1, the call will return NULL. Directly comparing such a cast to two Base1s will always return true, since NULL == NULL. Those two Base1s might not be the same object, though.
@ialiash -- If you allow me, I'll add my example (see my answer) to your answer and delete my answer.
3

Well, turns out the shortest way to achieve what you're looking for is:

assert(dynamic_cast<void*>(p1) == dynamic_cast<void*>(p2));

Dynamically casting to void* effectively down-casts the given pointer to its most derived class, so you're guaranteed if both point on the same object, the assert won't fail.

Indeed, there are practical uses for dynamic-casting to void pointer...

Edit: to answer the question's edit, the comparison is not safe. Consider the following code:

Base2 b2;
Base1 b1;
assert(static_cast<Derived*>(&b1) == static_cast<Derived*>(&b2));  // succeeds!

The memory layout of the two different bases is similar to that of a Derived (on a common implementation - the stack grows opposite of the heap). The first static_cast leaves the pointer as is, but the second one moves the pointer sizeof(Base1) back, so now they both point on &b1, and the assert succeeds - even though the objects are different.

You should use static_cast only if you know for sure the cast is correct. This is not your case, so you must use dynamic_cast, possibly as suggested above.

Comments

1

The short answer is no, this is generally not a good idea.

NOTE: This is assuming you want custom equivalence for all of your classes, if you want to check if they are the same object, it is better to do (Derived *).

A much better solution would be to overload the == operator for Base1, Base2, and Derived.

Assuming Base1 has 1 parameter param1 for equality and Base2 has another parameter param2 for equality:

virtual bool Base1::operator==(object& other){
    return false;
}

virtual bool Base1::operator==(Base1& other)
{
    return this.param1 == other.param1;
}

virtual bool Base2::operator==(object& other){
    return false;
}

virtual bool Base2::operator==(Base2& other)
{
    return this.param2 == other.param2;
}

virtual bool Derived::operator==(object& other){
    return false;
}

virtual bool Derived::operator==(Derived& other){
    return this.param1 == other.param1 && this.param2 == other.param2;
}

virtual bool Derived::operator==(Base1& other){
    return this.param1 == other.param1;
}

virtual bool Derived::operator==(Base2& other){
    return this.param2 == other.param2;
}

1 Comment

Sorry Lucretiel, I thought you were checking for object equality, not object identity. I'm leaving my answer up as a reference to future visitors, but you should accept ialiashkevich's answer
0

It appears to be invalid, based on this SO question: How is C++'s multiple inheritance implemented?

Basically, because of the way the objects are laid out in memory, a cast to either Base1* or Base2* results in a mutation of the pointer that I can't arbitrarily reverse at runtime without a dynamic_cast, which I'd like to avoid. Thanks everyone!

1 Comment

You can avoid dynamic_cast only if you know the type of the pointed object for sure. Please see my answer for more.
0

Use dynamic_cast, and watch out for NULL.

#include <cassert>

struct Base1 { virtual ~Base1() {} };
struct Base2 { virtual ~Base2() {} };
struct Derived : Base1, Base2 {};

bool IsEqual(Base1 *p1, Base2 *p2) {
  Derived *d1 = dynamic_cast<Derived*>(p1);
  Derived *d2 = dynamic_cast<Derived*>(p2);

  if( !d1 || !d2 ) return false;
  return d1 == d2;
}

int main () {
  Derived d;
  Base1 *p1 = &d;
  Base2 *p2 = &d;
  Base1 b1;
  Base2 b2;

  assert(IsEqual(p1, p2));
  assert(!IsEqual(p1, &b2));
  assert(!IsEqual(&b1, p2));
  assert(!IsEqual(&b1, &b2));
}

Comments

0
assert(p1 == p2);                      //This is illegal
assert(p1 == static_cast<Base1*>(p2)); //Is this ok?
assert(static_cast<Derived*>(p1) 
       == static_cast<Derived*>(p2));  //How about this?

None of them is a good solution. The first one will not compile, as you cannot compare pointers of unrelated types. The second one will not compile either (unless Base1 and Base2 are related through inheritance) for the same reason: you cannot static_cast to a pointer of an unrelated type.

The third option is borderline. That is, it is not correct, but it will work in many cases (as long as inheritance is not virtual).

The proper way of comparing for identity would be using dynamic_cast to the derived type and checking for null:

{
  Derived *tmp = dynamic_cast<Derived*>(p1);
  assert( tmp && tmp == dynamic_cast<Derived*>(p2) );
{

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.