多重继承指针比较

Multiple inheritance pointer comparison

本文关键字:比较 指针 多重继承      更新时间:2023-10-16

我有一个类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对象,反之亦然)

好吧,不,它不起作用。

我个人是举例学习的忠实粉丝,所以这里有一个:

#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

所以。。如果我们将 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++了,所以我可能有点偏离了我的游戏。温柔:))

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

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

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

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

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

事实上,动态投射到无效指针是有实际用途的......

编辑

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

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

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

只有当您确定强制转换正确时,才应使用static_cast。这不是您的情况,因此您必须使用 dynamic_cast ,可能如上所述。

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

注意:这是假设您希望所有类的自定义等效性,如果您想检查它们是否是同一对象,最好执行(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;
}

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

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

使用 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));
}
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到不相关类型的指针。

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

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

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