user396672
user396672

Reputation: 3166

how to check whether two pointers point to the same object or not?

Consider two pointers

A* a; 
B* b;

Both A and B are polymorphic classes. How to check whether a and b point to the same object or not?

More precisely, let's specify a and b point to the same object if there exists some object d of type D such that both *a and *b are somewhere in the class hierarchy of d.

I would propose the following solution:

dynamic_cast<void*>(a) == dynamic_cast<void*>(b)

Indeed, according to the standard,

dynamic_cast<void*>(v) 

yields ”a pointer to the most derived object pointed to by v. (n3242.pdf: § 5.2.7 - 7). If the most derived for both is the same object, then the pointers point to the same object.

I'm pretty sure that it should always work correctly from the practical viewpoint. But theoretically, at first glance the proposed equality seems to produce false positive, for example, in case if b points to the first member of A (not to A's ancestor). Although it's practically impossible to get equal addresses for A and its member since A's virtual table pointer should be located before this member, the standard doesn't mandate virtual tables and says nothing about the class layout.

So, my questions are:

  1. Is the proposed solution correct from the standard viewpoint?

  2. Are there any caveats about private (protected) inheritance or cv-qualification ?

  3. Are there better solutions?

[EDIT]

I tried to present some example that illustrates a relatively complex scenario. In this case dynamic crosscasting and static casting are ambiguous.

 // proposed impplementation:
template<typename P, typename Q> 
bool test_ptrs(const P* p, const Q* q)
{
  return (dynamic_cast<const void*>(p) ==  dynamic_cast<const void*>(q));
}


struct Root
{
  virtual ~Root(){};
};

struct A: public Root // nonvirtually
{
};

struct B: public Root // nonvirtually
{
};

struct C: public A, B  // nonvirtual diamond started with Root
{
  Root another_root_instance;
};

int main()
{
  C c;

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

  bool b = (dynamic_cast<void*>(pa) ==  dynamic_cast<void*>(pb));

  Root* pra= dynamic_cast<Root*> (pa); 
  Root* prb= dynamic_cast<Root*> (pb);

  //Root* prc= dynamic_cast<Root*> (&c); // runtime error, ambiguous cast
  Root* prr= dynamic_cast<Root*>(pra);

  Root* pcar= dynamic_cast<Root*>(pra);
  Root* pcbr= dynamic_cast<Root*>(prb);

  if(
      test_ptrs(pa, pb) 
      && test_ptrs(pra, prb)
      && !test_ptrs(pa,&c.another_root_instance)
    )
  {
    printf("\n test passed \n");
  }
}

Upvotes: 15

Views: 11738

Answers (3)

celtschk
celtschk

Reputation: 19731

Since with dynamic_cast you also can cast "sideways" in the type hierarchy, I would suggest:

(b != nullptr? dynamic_cast<B*>(a) == b : a == nullptr)

If a points to some subobject at the beginning of *b, then dynamic_cast<B*>(a) will necessarily return a null pointer (because there's no way a B contains itself). Therefore if b is not a null pointer, dynamic_cast<B*>(a) == b will succeed only if both share the same most derived class. The case that b is a null pointer has to be treated specifically because if a is not null, but does not point to a class derived from B, the dynamic_cast test will fail.

However there are some situations involving multiple inheritance where this solution will give a false negative (unlike your solution which never gives false negatives, but can give false positives). However the class hierarchies where this might happen are hierarchies which I'd say you shouldn't create anyway (namely the same derived class containing multiple indirect bases of type B). You can reduce the number of false negatives by testing again through exchanging the role of a and b (then only if both A and B are ambiguous in the most derived class, the test will fail).

You could also combine your and my test to give three results:

  • Both tests succeed: The pointers definitely are to the same object (or noth null).
  • Both tests fail: The pointers definitely are not to the same object.
  • Only my test fails: Either your test has given a false positive, or my test has given a false negative. You can't tell for sure whether both are the same object, but at least you can tell that you can't tell.

Upvotes: 0

Douglas Leeder
Douglas Leeder

Reputation: 53320

It would seem to me the least smelly way to deal with this is to introduce a base class for A & B:

#include <iostream>

struct Base
{
    virtual ~Base() {};
};

struct A : public virtual Base
{
    int a;
    virtual ~A() {};
    virtual void afunc() {};
};



struct B : public virtual Base
{
    int b;
    virtual ~B() {};
    virtual void bfunc() {};
};

struct C: A, B
{};

int main()
{
    C c;
    A *a = &c;
    B *b = &c;

    std::cout << "a* == " << &(*a) << std::endl;
    std::cout << "b* == " << &(*b) << std::endl;
    std::cout << "a == b == " << ((void*)a == (void*)b) << std::endl;

    Base* ba = a;
    Base* bb = b;

    std::cout << "ba* == " << &(*ba) << std::endl;
    std::cout << "bb* == " << &(*bb) << std::endl;
    std::cout << "ba == bb == " << (ba == bb) << std::endl;

    return 0;
}

Upvotes: 2

Akaanthan Ccoder
Akaanthan Ccoder

Reputation: 2179

I was trying to resolve this by comparing the address these pointers pointing to.

  • Address it is pointing to changes based on the type of pointer.

Hence theoretically we can say like

a* and b* points to the same object if there exists some object c of type C such that both *a and *b are somewhere in the class hierarchy of C."

Logically

we have to revisit the above statement like "a* and b* points to the same object but has it own zone of access in the memory of obj c of type C such that both *a and *b are somewhere in the class hierarchy of C.""

struct Aa { int a; Aa() {a= 0;} };

struct Bb 
{   int b;
    Bb() { b= 0;}
}; 
struct C: Aa, Bb {      
}; 

C c; 
Aa *a1 = &c; 
Aa *a2 = &c; 
Bb *b1 = &c; 
Bb *b2 = &c; 

cout  << &c << "\t"<< &(*a1)<<"\t"<< &(*a2)<<endl;
cout  << &c << "\t"<< &(*b1)<<"\t"<< &(*b2)<<endl;

Output:

  • &c 0x0012fd04
  • &(*a1) 0x0012fd04
  • &(*a2) 0x0012fd04
  • &(*b1) 0x0012fd08
  • &(*b2) 0x0012fd08

Though this will not solve your problem, we have a point to infer here.

Upvotes: 1

Related Questions