18

我有一个Derived直接从两个基类继承的类,Base1并且Base2. 我想知道一般来说,比较指向基类的指针以确定它们是否是同一个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对象,反之亦然)

4

7 回答 7

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

..所以总的来说,实例化一个派生类将为派生类的基类分配空间,最后为派生对象本身腾出空间。因为我们这里有 3 个整数,所以这将是 12 个字节。

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

您可能可以使用 RIIA 和 sizeof() 来做一些魔术,并确定偏移量 b2 应该有多少与 b1 相当,但随后您会遇到各种其他麻烦,例如虚拟。简而言之,我不会推荐这种方法。

更好的方法是像 ialiashkevich 所说的那样强制转换为 Derived*,但是,如果您的对象不是 Derived* 的实例,这将带来问题。

(免责声明;我已经有 3 到 4 年没有使用 C++了,所以我可能有点偏离我的游戏。要温柔 :))

于 2012-06-28T21:53:02.430 回答
6

Derived*在比较之前转换为正确的方法。

有一个类似的话题:C++ 指针多继承乐趣

于 2012-06-28T21:36:52.007 回答
2

好吧,事实证明,实现您正在寻找的最短方法是:

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

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

事实上,动态强制转换为 void 指针有实际用途......

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

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

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

static_cast仅当您确定演员表正确时才应使用。这不是你的情况,所以你必须使用dynamic_cast,可能如上所述。

于 2012-06-29T06:39:43.153 回答
1

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

注意:这是假设您想要为所有类自定义等价,如果您想检查它们是否是同一个对象,最好这样做(Derived *)

一个更好的解决方案是重载、和的==运算符。Base1Base2Derived

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

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;
}
于 2012-06-28T21:26:39.643 回答
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?

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

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

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

{
  Derived *tmp = dynamic_cast<Derived*>(p1);
  assert( tmp && tmp == dynamic_cast<Derived*>(p2) );
{
于 2012-06-28T22:34:25.513 回答
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));
}
于 2012-06-28T21:57:09.850 回答
0

基于这个 SO 问题,它似乎是无效的: C++ 的多重继承是如何实现的?

基本上,由于对象在内存中的布局方式,强制转换为Base1*orBase2*导致指针的突变,如果没有 a ,我无法在运行时任意反转dynamic_cast,我想避免这种情况。感谢大家!

于 2012-06-28T21:51:02.300 回答