简体   繁体   English

用不同的 arguments 重载 function

[英]Overload function with different arguments

Hope you can help me out with this.希望你能帮我解决这个问题。 Consider the following class hierarchy:考虑以下 class 层次结构:

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.
}

In another class I've got a pointer to the parent class that creates a new instance of one of the two child classes, depending on an enum value, like so:在另一个 class 中,我有一个指向父 class 的指针,它根据枚举值创建两个子类之一的新实例,如下所示:

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

Please note I've simplified the function so you get an idea of how my code works.请注意,我已经简化了 function,以便您了解我的代码是如何工作的。 Now what I want to do is a function that updates the member variables of each Child class, radius for CircleCollider , and size for RectCollider .现在我想做的是一个 function 更新每个 Child class 的成员变量, RectColliderradiusCircleCollidersize I thought of adding a function to the Collider class:我想在对撞机 class 中添加一个 function:

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

And then define it in each child class.然后在每个子 class 中定义它。

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

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

The problem I see with this is that the definition used in CircleCollider will not use the NewSize parameter, and the same will happen to NewRadius with the definition in RectCollider .我看到的问题是CircleCollider中使用的定义不会使用NewSize参数,同样的情况也会发生在NewRadiusRectCollider中的定义。 However, I cannot think of another way to do it.但是,我想不出另一种方法来做到这一点。 Do you know another way to do it making use of the hierarchy and polymorphism present in this code?您是否知道另一种利用此代码中存在的层次结构和多态性的方法? Thank you very much in advance!非常感谢您!

I would recommend using std::variant , if you have access to C++17 or later.如果您可以访问 C++17 或更高版本,我建议您使用std::variant

Of course, a poor man's std::variant would probably be a union :当然,穷人的std::variant可能是 union

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

Of course, if you really want to make it extendable for other things, not just circles and rectangles, you also have std::any .当然,如果你真的想让它扩展到其他东西,而不仅仅是圆形和矩形,你也有std::any

You can use a base class function accepting a pointer to Collider as an input parameter.您可以使用基础 class function 接受指向 Collider 的指针作为输入参数。 Then use dynamic_cast to determine the type of the Collider object and update the parameter accordingly.然后使用 dynamic_cast 确定 Collider object 的类型并相应地更新参数。

Something like this,像这样的东西,

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;
    }
}

Of course, you'll need some accessor for radius_ and size_ to compile, or else you need them as public members.当然,您需要一些用于 radius_ 和 size_ 的访问器来编译,否则您需要它们作为公共成员。

When you have classes that need very different kinds of data to construct and update, the only thing that can be commonly used to construct and update objects of those types is a std::string or a stream-like object.当您的类需要非常不同类型的数据来构造和更新时,唯一可用于构造和更新这些类型的对象的常用方法是std::string或类似流的 object。 That will allow you the generality that can be supported at the base class and the specificity needed by the derived classes.这将允许您在基础 class 支持的通用性和派生类所需的特异性。

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;
    }
  }
}

and

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

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

It's easy to construct a std::istringstream from a std::string and pass them to the above functions.std::string构造std::istringstream并将它们传递给上述函数很容易。

Although the other answers address ways that this could work, I would be inclined to take a step back and consider your abstractions.尽管其他答案解决了可行的方法,我倾向于退后一步考虑您的抽象。 From a design perspective, if you find yourself in a situation where parameters are only orthogonal for different implementations, then you may not have chosen an effective abstraction from the start.从设计的角度来看,如果您发现自己处于参数仅对不同实现正交的情况,那么您可能从一开始就没有选择有效的抽象。

You should consider whether the UpdateCollider needs to be virtual at all.您应该考虑UpdateCollider是否需要是虚拟的。 Judging by the enum of ECollisionType , this appears to be a fixed number of types.ECollisionType的枚举来看,这似乎是固定数量的类型。 In which case, you could re-design your Collider implementations such that each of them have public member functions that satisfy their goals, eg:在这种情况下,您可以重新设计您的Collider实现,使它们中的每一个都具有满足其目标的公共成员函数,例如:

class CircleCollider : public Collider
{
private:
   // ...

public:
   void setRadius(...);
}

class RectCollider : public Collider
{
private:
  // ..

public:
  void setSize(...);
}

And then, rather than updating in some Collider::UpdateCollider abstract function, you can do the update from outside (such as in some ColliderManager ) by casting to the correct underlying type, and calling the specific setters required for that type.然后,与其在某些Collider::UpdateCollider抽象 function 中进行更新,不如从外部(例如在某些ColliderManager中)通过转换为正确的底层类型并调用该类型所需的特定设置器来进行更新。 There are two possible ways I can think of to do this:我可以想到两种可能的方法来做到这一点:

  • using dynamic_cast to poll for the correct type (creates an if / else if ladder)使用dynamic_cast轮询正确的类型(创建if / else if阶梯)
  • using your ECollisionType to designate a program invariant, which assumes that ECollisionType is always accurately specifying the correct Collider type -- then using static_cast to the correct type (note: This is technically less-safe if ever your invariant isn't upheld, since it could lead to Undefined Behavior -- but would be faster since it doesn't require querying RTTI).使用你的ECollisionType来指定一个程序不变量,它假定ECollisionType总是准确地指定正确的Collider类型 - 然后使用static_cast到正确的类型(注意:如果你的不变量没有得到支持,这在技术上不太安全,因为它可能导致未定义的行为——但会更快,因为它不需要查询 RTTI)。

For example:例如:

// 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(...);
    }
    ...
  }

}  

Whether this approach works or not would depend entirely on your design.这种方法是否有效完全取决于您的设计。 If you intend to allow consumers to extend and add Collider types, then this likely isn't an effective approach.如果您打算允许消费者扩展和添加Collider类型,那么这可能不是一种有效的方法。 However if it's always fixed and internalized, then a good abstraction is to have a *Manager (such as ColliderManager ) take care of this.但是,如果它始终是固定的和内部化的,那么一个好的抽象是让一个*Manager (例如ColliderManager )来处理这个问题。 The concern of the manager would be updating, and managing all possible collider types -- which doesn't violate any abstraction by having it be aware of the appropriate underlying type.管理器的关注点是更新和管理所有可能的碰撞器类型——这不会违反任何抽象,因为它知道适当的底层类型。 This approach can also allow more complex setters for more complex colliders, and the ColliderManager would be responsible for massaging the data so that it coherently works for the respective Collider这种方法还可以为更复杂的碰撞器提供更复杂的设置器,并且ColliderManager将负责处理数据,以便它为相应的Collider连贯地工作

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM