【问题标题】:Overload function with different arguments具有不同参数的重载函数
【发布时间】:2019-11-19 21:44:04
【问题描述】:

希望你能帮我解决这个问题。考虑以下类层次结构:

class Collider
{
  public:
  ... //Some functions that aren't worth mentioning. They all are abstract so the class is abstract.
}

class CircleCollider : public Collider
{
  private:
  vec2 pos_;
  float radius_;

  public:
  ... //The definition of the parent functions and constructor / destructor.
}

class RectCollider : public Collider
{
  private:
  vec2 pos_;
  vec2 size_;

  public:
  ... // Again, the definition of the parent functions and constructor / destructor.
}

在另一个类中,我有一个指向父类的指针,它根据枚举值创建两个子类之一的新实例,如下所示:

void SetCollisionType(ECollisionType Type)
{
  switch(Type)
  {
    case CIRCLE:
    {
       Collider* collider = new CircleCollider(pos, radius);
       break;
    }
    case RECTANGLE:
    { 
       Collider* collider = new RectCollider(pos, size);
       break;
    }
  }
}

请注意,我已经简化了函数,以便您了解我的代码是如何工作的。现在我要做的是更新每个子类的成员变量的函数,radius 用于CircleCollidersize 用于RectCollider。我想在 Collider 类中添加一个函数:

virtual void UpdateCollider(float NewRadius, vec2 NewSize) = 0;

然后在每个子类中定义。

   void CircleCollider::UpdateCollider(float NewRadius, vec2 NewSize)
   { 
     radius_ = NewRadius;
   }

   void RectCollider::UpdateCollider(float NewRadius, vec2 NewSize)
   {
     size_ = NewSize;
   }

我看到的问题是CircleCollider 中使用的定义不会使用NewSize 参数,NewRadius 也会发生同样的情况,RectCollider 中的定义。但是,我想不出另一种方法来做到这一点。您是否知道另一种利用此代码中存在的层次结构和多态性的方法?非常感谢您!

【问题讨论】:

  • 这实际上取决于您如何调用该方法。这两个不同的论点是否有些相关?也许半径是尺寸的欧几里得长度?调用者应该知道它在哪个对撞机上运行,​​或者它应该对两者使用相同的输入,从中计算两个不同的参数。在后一种情况下,您应该能够在对撞机中移动此计算。
  • 注意:像{ Collider* collider = new CircleCollider(pos, radius); } 这样的东西几乎肯定会造成内存泄漏,因为collider 几乎会立即超出范围。希望这部分已经简化,实际上不在您的代码中。
  • @Chipster 哦,当然!实际上对撞机是我的类“Sprite”的成员变量。我只是想显示变量类型以防有任何疑问^^谢谢您的回答!

标签: c++ polymorphism hierarchy


【解决方案1】:

如果您可以访问 C++17 或更高版本,我建议您使用 std::variant

当然是穷人的std::variantwould probably be a union

union UpdateArg {
    float NewRadius;
    vec2 NewSize;
};
//...
void RectCollider::UpdateCollider(UpdateArg NewSize) {
    //...
}

当然,如果你真的想让它扩展到其他东西,而不仅仅是圆形和矩形,你也有std::any

【讨论】:

  • 我还没有使用工会,所以我不知道它们是如何工作的:S 无论如何感谢您的帮助!我会看看他们:D
【解决方案2】:

您可以使用接受指向 Collider 的指针作为输入参数的基类函数。然后使用 dynamic_cast 确定 Collider 对象的类型,并相应地更新参数。

类似的,

void Collider::UpdateCollider(Collider *p, float NewRadius, vector NewSize)
{
    if(CircleCollider *p_circle_collider = dynamic_cast<CircleCollider *>(p))
    {
        // this is a CircleCollider object
        p_circle_collider->radius_ = NewRadius;
    }
    else if( RectCollider *p_rect_collider = dynamic_cast<RectCollider *>(p))
    {
        // this is a RectCollider object
        p_rect_collider->size_ = NewSize;
    }
}

当然,您需要一些 radius_ 和 size_ 的访问器来编译,否则您需要它们作为公共成员。

【讨论】:

    【解决方案3】:

    当您的类需要非常不同类型的数据来构造和更新时,唯一可以用来构造和更新这些类型的对象的常用方法是std::string 或类似流的对象。这将为您提供基类可以支持的通用性和派生类所需的特异性。

    void SetCollisionType(ECollisionType Type,
                          std::istream& is)
    {
      switch(Type)
      {
        case CIRCLE:
        {
           float radius;
           is >> radius;
           Collider* collider = new CircleCollider(pos, radius);
           break;
        }
        case RECTANGLE:
        { 
           vec2 size;
           // Assuming such an overload exists.
           is >> size;
           Collider* collider = new RectCollider(pos, size);
           break;
        }
      }
    }
    

    void CircleCollider::UpdateCollider(std::istream& is)
    { 
       is >> radius_;
    }
    
    void RectCollider::UpdateCollider(std::istream& is)
    {
       is >> size_;
    }
    

    std::string 构造std::istringstream 并将它们传递给上述函数很容易。

    【讨论】:

    • 性能如何?直观地说,传递一些浮点数应该比构造字符串、字符串流和再次提取数字快得多。
    • @n314159,在一个紧密的循环中会很糟糕。否则不会有任何问题。
    【解决方案4】:

    虽然其他答案解决了这种可能工作的方式,但我倾向于退后一步考虑你的抽象。从设计的角度来看,如果您发现自己处于参数仅对不同实现正交的情况,那么您可能从一开始就没有选择有效的抽象。

    您应该考虑UpdateCollider 是否需要是虚拟的。从ECollisionType 的枚举来看,这似乎是固定数量的类型。在这种情况下,您可以重新设计您的 Collider 实现,使它们中的每一个都具有满足其目标的公共成员函数,例如:

    class CircleCollider : public Collider
    {
    private:
       // ...
    
    public:
       void setRadius(...);
    }
    
    class RectCollider : public Collider
    {
    private:
      // ..
    
    public:
      void setSize(...);
    }
    

    然后,您可以通过转换为正确的底层类型并调用该类型所需的特定设置器来从外部(例如在某些 ColliderManager 中)进行更新,而不是在某些 Collider::UpdateCollider 抽象函数中进行更新.我可以想到两种可能的方法来做到这一点:

    • 使用dynamic_cast 轮询正确的类型(创建if/else if 阶梯)
    • 使用ECollisionType 来指定程序不变量,它假定ECollisionType 总是准确地指定正确的Collider 类型——然后使用static_cast 来指定正确的类型(注意:这在技术上不太安全如果不支持您的不变量,因为它可能导致未定义的行为 - 但会更快,因为它不需要查询 RTTI)。

    例如:

    // Using dynamic_cast
    void ColliderManager::dynamicColliderUpdater(Collider& c) {
      if (auto* p = dynamic_cast<CircleCollider&>(&c) {
        p->setRadius(...);
      } else if (auto* p = dynamic_cast<RectCollider&>(&c) {
        p->setSize(...);
      } 
      ...
    }
    
    // Using static cast
    void ColliderManager::staticColliderUpdater(Collider& c, EColliderType t)
    {
      switch (t) {
        case CIRCLE: {
          static_cast<CircleCollider&>(c).setRadius(...);
        }
        case RECTANGLE: {
          static_cast<RectCollider&>(c).setSize(...);
        }
        ...
      }
    
    }  
    

    这种方法是否有效完全取决于您的设计。如果您打算允许消费者扩展和添加Collider 类型,那么这可能不是一种有效的方法。但是,如果它始终是固定的和内部化的,那么一个好的抽象是让*Manager(例如ColliderManager)来处理这个问题。管理器关心的是更新和管理所有可能的对撞机类型——这不会违反任何抽象,因为它知道适当的底层类型。这种方法还可以为更复杂的碰撞器提供更复杂的设置器,ColliderManager 将负责处理数据,以便它为各自的Collider 连贯地工作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-07-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-05
      相关资源
      最近更新 更多