【问题标题】:Multiple inheritance pointer comparison多重继承指针比较
【发布时间】:2012-06-30 11:20:18
【问题描述】:

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

【问题讨论】:

  • 我的印象是c++不支持多重继承。你的意思是“Base1->Base2->Derived”
  • C++ 确实支持多重继承 :)
  • 这是最简单的方法之一,但是是的,C++ 支持 MI
  • "这是射中自己脚的最简单方法之一"不。

标签: c++ inheritance pointers multiple-inheritance


【解决方案1】:

好吧,不,这行不通。

我个人非常喜欢通过示例学习,所以这里有一个:

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

【讨论】:

    【解决方案2】:

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

    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) 向后移动,所以现在它们都指向 &amp;b1,并且断言成功 - 即使对象不同。

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

    【讨论】:

      【解决方案3】:
      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 用于派生类型并检查是否为空:

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

      【讨论】:

        【解决方案4】:

        使用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));
        }
        

        【讨论】:

          【解决方案5】:

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

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

          【讨论】:

          • 对不起 Lucretiel,我以为你在检查对象相等性,而不是对象身份。我留下我的答案作为对未来访问者的参考,但你应该接受 ialiashkevich 的回答
          【解决方案6】:

          这似乎是无效的,基于这个 SO 问题: How is C++'s multiple inheritance implemented?

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

          【讨论】:

          • 只有明确知道指向对象的类型,才能避免dynamic_cast。请参阅我的回答了解更多信息。
          【解决方案7】:

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

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

          【讨论】:

          • “指针保证有效,但不一定指向派生对象”。这并不是说您的建议不起作用,但您必须使用dynamic_cast,并且您必须处理它们都返回 NULL 的情况(这可能意味着也可能不意味着它们指向同一个对象)。
          • 哦,它们永远不会为空——我想我提到过。它们保证指向其类型的有效对象。
          • @Lucretiel,如果您调用dynamic_cast&lt;Derived*&gt;(p) 并且p 指向Base1 类型的有效对象,则调用将返回NULL。因为NULL == NULL,直接将这样的演员表与两个Base1s 进行比较将始终返回true。不过,这两个 Base1s 可能不是同一个对象。
          • @ialiash -- 如果您允许,我会将我的示例(请参阅我的答案)添加到您的答案中并删除我的答案。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-01-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-06-13
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多