如何检查两个指针是否指向同一个对象?

15
考虑两个指针
A* a; 
B* b;

A和B都是多态类。如何检查a和b是否指向同一对象?

更精确地说,如果存在类型为D的对象d,使得*a和*b都在d的类层次结构中,则指定a和b指向同一对象。

我建议采用以下解决方案:

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

的确,根据标准,

dynamic_cast<void*>(v) 

产生yield的结果是“指向 v 所指对象的最派生对象的指针”(引自n3242.pdf: §5.2.7 - 7)。

如果两个指针都指向同一个最派生对象,那么这两个指针指向同一个对象。

从实用角度看,我相信这个提议应该始终能正常工作。但从理论上来看,乍一看这个提议似乎会产生误判,例如当 b 指向 A 的第一个成员(而不是A的祖先)时。虽然因为A的虚函数表指针应该位于这个成员之前,A和其成员获得相同地址在实践中几乎不可能,但标准并未规定虚函数表,并且对类布局没有任何要求。

所以,我的问题如下:

  1. 从标准的角度来看,这个提议是否正确?

  2. 私有 (protected) 继承或 cv-qualification 是否存在任何注意事项?

  3. 是否有更好的解决方案?

[编辑]

我试图展示一个说明相对复杂情境的例子。在这种情况下,动态交叉转换和静态转换是有歧义的。

 // 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");
  }
}

12
为什么不是 a == b - iammilind
2
+1 给 @iammilind - 老的就是最好的! - Martin James
2
@user396672:既然您坚持认为a == b不足以满足您的情况,我很想看到一个简单的自编译示例程序(特别是指针的静态类型和类层次结构),以比英语更准确地说明这种情况。 - Frerich Raabe
1
比较不相关类型的指针被认为是代码异味,应该避免。 - iammilind
13
考虑以下代码:struct A { int a; virtual ~A(); }; struct B { int b; virtual ~B(); }; struct C: A, B {}; int main() { C c; A *a = &c; B *b = &c; }。现在,尽管ab是指向同一对象的不同基类,但它们不能进行比较。(void*)a != (void*)b,但是dynamic_cast<void*>(a) == dynamic_cast<void*>(b)。这就是指针比较和提问者询问的问题之间的差异。 - Steve Jessop
显示剩余11条评论
3个回答

2

我认为处理这个问题最简洁的方式是为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;
}

我同意它是最不臭的,尽管严格来说,这并不是一个答案 :)。 - user396672

1

我试图通过比较这些指针所指向的地址来解决这个问题。

  • 它所指向的地址根据指针类型而变化。

因此,理论上我们可以说:

如果存在某个类型为C的对象c,使得*a和*b都在C类层次结构中的某个地方,则a*和b*指向同一对象。

逻辑上,我们必须重新审视上述语句,如下所示:

"a*和b*指向同一对象但在类型为C的obj c的内存中有自己的访问区域,使得*a和*b都在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;

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

虽然这并不会解决你的问题,但我们有一个推断点。


0

由于使用 dynamic_cast 时,您还可以在类型层次结构中进行“侧向”转换,因此我建议:

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

如果a指向*b的某个子对象的开头,则dynamic_cast<B*>(a)将必然返回空指针(因为没有一种方式可以使B包含自身)。因此,如果b不是空指针,则只有当两者共享相同的最派生类时,dynamic_cast<B*>(a) == b才会成功。需要特别处理b是空指针的情况,因为如果a不为空,但不指向从B派生的类,则dynamic_cast测试将失败。
然而,在涉及多重继承的某些情况下,这种解决方案会产生错误的负面效果(与您的解决方案不同,后者永远不会产生错误的负面效果,但可能会产生错误的正面效果)。但是,可能发生这种情况的类层次结构是我认为您不应该创建的层次结构(即相同的派生类包含多个类型为B的间接基类)。通过交换ab的角色再次进行测试,可以减少错误的负面效果(只有在两个 AB在最派生类中都是模糊的情况下,测试才会失败)。

你也可以将你的测试和我的测试结合起来,得出三个结果:

  • 两个测试都成功:指针明确指向同一个对象(或者都为 null)。
  • 两个测试都失败:指针明确不指向同一个对象。
  • 只有我的测试失败:要么你的测试给出了错误的正面结果,要么我的测试给出了错误的反面结果。你无法确定它们是否指向同一个对象,但至少你知道你无法确定。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接