多重继承指针比较

时间:2012-06-28 21:22:19

标签: c++ inheritance pointers multiple-inheritance

我有一个类Derived,它直接从两个基类Base1Base2继承。我想知道一般来说,将指针与基类进行比较以确定它们是否是相同的Derived对象是否安全:

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?

指针保证有效,但不一定指向Derived对象。我的猜测是这可能很好,但我想从技术C ++的角度来看它是否可以。我实际上从不对指针进行任何操作,我只是想知道它们是否指向同一个对象。

编辑:如果我可以保证p1p2指向Derrived个对象,那似乎是安全的。我基本上想知道如果它们不是 - 如果一个或两个都指向一个基础对象,它是否安全,那么比较是否会失败?同样,我可以保证指针有效(即,p1永远不会指向Base2个对象,反之亦然)

7 个答案:

答案 0 :(得分:6)

嗯,不,它不起作用。

我个人非常喜欢逐个学习,所以这里有一个:

#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;
}

我的电脑上有一个输入输出:

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

Soo ..如果我们将d的地址定义为0,则b1为0,b2为+4,d的数量为+8。这是因为我机器上的int长4个字节。

基本上,您必须查看C ++内部如何表示类的布局:

Address:    Class:
0           Base1
4           Base2
8           Derived

..总而言之,实例化Derived类将为派生类的基类分配空间,最后为派生对象本身腾出空间。由于这里有3个整数,所以这将是12个字节。

现在,你要问的是什么(除非我误解了一些东西)是你可以比较不同基类指针的地址,看看它们是否指向同一个对象,答案是否定的 - 不直接至少,在我的例子中,b1将指向0035F9FC,而b2将指向0035FA00。在C ++中,这种偏移都是在编译时完成的。

你可能会对RIIA和sizeof()做一些魔术,并确定偏移量b2应该与b1相当多少,但是你会遇到各种其他问题,比如虚拟。简而言之,我不推荐这种方法。

更好的方法是投射到Derived *,就像ialiashkevich所说的那样,如果您的对象不是Derived *的实例,则会产生问题。

(免责声明;我在3 - 4年内没有使用过C ++,所以我的游戏可能有些偏差。温柔:))

答案 1 :(得分:5)

在比较之前投射到Derived*是正确的方法。

有一个类似的主题:C++ pointer multi-inheritance fun

答案 2 :(得分:1)

简短的回答是否定的,这通常不是一个好主意。

注意:这是假设您希望所有类的自定义等效,如果要检查它们是否是同一个对象,最好是(Derived *)

更好的解决方案是重载==Base1Base2的{​​{1}}运算符。

假设Derived有1个参数Base1表示相等,param1有另一个参数Base2表示相等:

param2

答案 3 :(得分:1)

嗯,结果是找到你想要的最短路径:

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

动态转换为void*有效地将给定指针向下转换为其最派生类,因此如果两者都指向同一对象,则保证断言不会失败。

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

编辑:要回答问题的编辑,比较不是安全。请考虑以下代码:

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

两个不同基数的内存布局类似于Derived的内存布局(在常见实现上 - 堆栈与堆相反)。第一个static_cast将指针保持原样,但第二个指针将指针sizeof(Base1)移回,所以现在它们都指向&b1,并且断言成功 - 即使对象不同

只有在您确定演员表是正确的情况下才应使用static_cast。这不是您的情况,因此您必须使用dynamic_cast,可能如上所述。

答案 4 :(得分:0)

基于此SO问题,它似乎无效: How is C++'s multiple inheritance implemented?

基本上,由于对象在内存中的布局方式,对Base1*Base2*的强制转换会导致指针突变,我无法在运行时任意反转dynamic_cast,我想避免。谢谢大家!

答案 5 :(得分:0)

使用dynamic_cast,注意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));
}

答案 6 :(得分: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?

它们都不是好的解决方案。第一个不会编译,因为你无法比较不相关类型的指针。第二个也不会编译(除非Base1Base2通过继承相关),原因相同:你不能static_cast指向不相关类型的指针。

第三个选项是 borderline 。也就是说,它不正确,但它在许多情况下都会起作用(只要继承不是虚拟的)。

比较身份的正确方法是使用dynamic_cast来派生类型并检查null:

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