【问题标题】:How do I make virtual methods of classes that inherit the same base class figure out what to do with their argument?如何使继承相同基类的类的虚拟方法弄清楚如何处理它们的参数?
【发布时间】:2012-12-09 23:11:16
【问题描述】:

这里用代码解释:

//Sphere and Box inherit from IShape
//
//Sphere methods:
//bool Sphere::Intersect(Sphere* sphere)
//bool Sphere::Intersect(Box* box);
//
//Box methods:
//bool Box::Intersect(Sphere* sphere)
//bool Box::Intersect(Box* box)

IShape* shapeA;
IShape* shapeB;

shapeA= new Sphere();
shapeB= new Box();

bool areTheyIntersecting = shapeA->Intersect(shapeB); //problem is here?

这种使用多态性的方法会起作用吗,还是我必须寻找另一种方法让类识别彼此的类型,以便它们知道要调用的正确方法?

【问题讨论】:

  • 这里需要双重调度...
  • 为什么不直接使用静态类型呢? IShape 是一个非常糟糕的主意。
  • 所以我可以稍后将它们(IShape 变量)放在 1 个数组中,以便更轻松地制作 CompositeShape

标签: c++ inheritance polymorphism abstract-class


【解决方案1】:

对于这种情况,您需要一种称为双重调度的东西。看看 Scott Meyers 的 More Effective C++ 和 Alexendrescu 的 Modern C++ Design。

这是另一篇也讨论它的文章 http://www.drdobbs.com/double-dispatch-revisited/184405527

【讨论】:

    【解决方案2】:

    您所描述的问题称为双重调度。有很多解决方案。它们都以各种方式吸吮。

    它们糟糕的根本原因是双重调度的大小随着类型的数量呈二次方增长,而编写二次方数量的代码总是很糟糕。

    一种方法是找出一种方法,将一般的双重分派减少为具有一些特殊情况(如球体球体)的单一分派。

    如果类型数量有限,可以有两组虚函数。第一个将 other 作为抽象基类。另一组函数将每个实现类作为可能的参数。抽象基类在每个实现中都实现,调用另一个对象,并将 this 作为不将抽象基作为参数的“其他函数集”的第一个参数。

    collide(base* other) 调用other->collideSpecific (this)

    CRTP 可用于减少一些样板。

    【讨论】:

      【解决方案3】:

      解决它的一种方法是在每个类中声明 uint 变量(或从类中提取,见下文)和一个为相关对象返回此变量的虚函数。然后我会声明<std::pair<uint,uint>,std::function<bool(IShape*, IShape*)>>的哈希值

      哈希将包含知道如何处理特定对象类型的函数,典型的调用如下:

      auto key = std::pair<uint,uint>(obj1.typeNumber(), obj2.typenumber())
      funchash[key](obj1, obj2);
      

      数字甚至不需要硬编码,如本手册所示: http://shaderop.com/2010/09/uniquely-identifying-types-in-c-without-using-rtti/index.html 这实质上展示了如何将类名转换为类型 id。

      当然,这仍然意味着很多样板,但我不喜欢在我的类中使用它,而且这种类型机制本身还是很有用的。

      【讨论】:

      • 这其实是对virtual dispatch的重新实现;没有编译器支持。适用于封闭的层次结构,可以扩展到任意层次结构,前提是您有一种生成 ID 的方法以及一种在查找表中注册方法的方法。
      • 我喜欢这种方法,因为它意味着盒子和球体都不必知道彼此的结构才能计算交集。拥有处理这些事情的免费函数非常方便,特别是因为您可以在运行时更改它们
      【解决方案4】:

      双重分派通常通过Visitor Pattern 使用,但在您的情况下,这有点奇怪,因为访问者就是元素本身...等等!

      简单地说:

      • 从现在开始从一对IShape/IShape开始,命名为leftright
      • left 上调用intersect 方法,将right 作为参数传递,在intersect 方法中left 知道它确切 类型(它是Sphere!)并通知right 通过在right 上调用intersect 的正确重载来解决这个问题(采用Sphere 的那个)
      • 在对rightintersect 调用中,right 知道它的确切类型,我们也传递了left 的确切类型,所以现在我们都知道了!李>

      一个简单的实现:

      class IShape {
      public:
          virtual ~IShape() {}
      
          virtual bool intersect(IShape const& other) const = 0;
      
          virtual bool intersect(Box const& other) const = 0;
          virtual bool intersect(Sphere const& other) const = 0;
      }; // class IShape
      
      class Box: public IShape {
      public:
          virtual bool intersect(IShape const& other) const override {
              std::cout << "-> Box::intersect(IShape)\n";
              return other.intersect(*this);
          }
      
          virtual bool intersect(Box const& other) const override {
              std::cout << "-> Box::intersect(Box)\n";
              /* compute intersection of two boxes */
              return false;
          }
      
          virtual bool intersect(Sphere const& other) const override {
              std::cout << "-> Box::intersect(Sphere)\n";
              /* compute intersection of a box and a sphere */
              return false;
          }
      }; // class Box
      
      // Likewise implementation of Sphere
      

      complete implementation 给出以下输出:

      int main() {
         Box const box;
         Sphere const sphere;
         IShape const& ibox = box;
         IShape const& isphere = sphere;
      
      
         box.intersect(sphere);
         // output
         // -> Box::intersect(Sphere)
      
      
         ibox.intersect(sphere);
         // output
         // -> Box::intersect(Sphere)
      
      
         sphere.intersect(ibox);
         // output
         // -> Sphere::intersect(IShape)
         // -> Box::intersect(Sphere)
      
      
         isphere.intersect(ibox);
         // output
         // -> Sphere::intersect(IShape)
         // -> Box::intersect(Sphere)
      }
      

      这种策略之所以命名为双分派,是因为从 IShape/IShape 开始时有两个连续的双分派,每个变量一个分派,需要为其“推导”动态类型。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-04-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多