【问题标题】:How can I access to derived members from a pure virtual base class function?如何从纯虚拟基类函数访问派生成员?
【发布时间】:2019-12-29 15:43:00
【问题描述】:

我想要一个 Collider 接口类,其中将有一个重载的 -> 运算符来直接访问 BoxCollider 派生类。我想通过界面访问 box collider 的成员并在运行时更改对撞机的类型。 所以我想到了使用模板:

template<typename T>
class ColliderV2 {
public:
    virtual T* operator ->() = 0;
};

class BoxColliderV2 : public ColliderV2<BoxColliderV2> {
public:
    float width;
    float height;

    BoxColliderV2* operator ->() {
        return this;
    }


};
int main()
{
    ColliderV2<BoxColliderV2>* col = new BoxColliderV2;
    (*col)->width = 1; 

}

这行得通。但是据我所知,templates 会在编译时用 Box Collider 填充 T 生成一个全新的 Collider 类,对吗?这就是它起作用的原因。但后来它阻止我改变对撞机类型。我还想过在派生类BoxCollider* operator-&gt;() ;中创建一个带有Collider* operator-&gt;() ;重载的虚拟碰撞器类

但如果我尝试过:

Collider<BoxCollider>* col = new BoxCollider;
(*col)->width = 1; // won't work

由于 Collider 不是 BoxCollider,因此不起作用。而且我不想 dynamic_cast 我可能拥有的所有可能的对撞机类型。那么,这里可以做什么呢?

【问题讨论】:

  • 请注意并更正示例代码中的类型名称。就目前而言,工作和非工作示例都完全相同(据我所知)。
  • 是的,最后一个例子应该是Collider&lt;BoxCollider&gt;* col = new BoxCollider;。错字。马上改正。
  • V2 对您的问题重要吗?
  • @Ulrich Eckhardt 不是真的,我只是在创建一些类并同时测试一个又一个。因此,为了不具有同名的类,我在名称的末尾添加了 V2。但是我问问题时忘记删除了。
  • 在分配(*col)-&gt;width = 1; 时,您如何期望知道*col 是具有width 成员的(动态)类型?在某个地方,您需要以某种方式做出决定,而当您这样做时,它可能会为必要的解决方案提供线索。

标签: c++ templates interface polymorphism


【解决方案1】:

正如您已经发现的那样,这是行不通的。模板和运行时行为是一种矛盾的机制。您不能创建一个公共基类并让它像一个通用指针一样让您访问其派生类型的成员。

接口指定您可以编码的合同。您不会针对特定的实现进行编码,而是针对接口进行编码,因此接口必须提供您想要访问的所有成员。在您的情况下,这将导致widthheight 成为ColliderV2 的一部分,而不是BoxColliderV2。但是,这会破坏您试图模仿的逻辑。

您可以采取以下几种方法:

  1. 要么让你的对撞机类型变体,比如

    using ColliderType = std::variant<BoxColliderV2, MyOtherCollider, ...>;
    

    并在您要访问成员时检查实际类型

    ColliderType myCollider = /* generate */;
    
    if (auto boxCollider = std::get_if<BoxColliderV2>(&myCollider); boxCollider)
        boxCollider->width = 0;
    
  2. 或者,保留您拥有的基类,删除 operator-&gt; 和模板并对其进行动态转换:

    ColliderV2* col = new BoxColliderV2;
    if (auto boxCollider = dynamic_cast<BoxColliderV2*>(col); boxCollider)
        boxCollider->width = 0;
    
  3. 您还可以将widthheight 等详细信息隐藏在属于界面一部分的更通用函数后面。例如:

    class ColliderV2 {
    public:
        virtual void setBounds(float width, float height) = 0;
    };
    
    class BoxColliderV2 : public ColliderV2 {
    public:
        void setBounds(float width, float height) override {
            this->width = width;
            this->height = height;
        }
    
    private:
        float width;
        float height;
    };
    
    int main()
    {
        ColliderV2* col = new BoxColliderV2;
        col->setBounds(1, 1); 
    }
    

【讨论】:

    【解决方案2】:

    C++ 不鼓励您尝试做的事情。您要做的是根据函数的返回值更改某些内容的类型。类型系统设计是为了阻止您编写这样的代码。
    一个函数的一个重要限制是它只能返回一种类型的东西。如果您将这些可能性包装在一个类中,您可以返回一个事物列表,然后返回它。在 C++17 中,一个现成的类是std::variant。对此的限制是事物列表必须是固定的(或封闭集)。如果您想要任意一组返回值(开放集),则必须使用不同的方法。您必须用对返回值执行的函数来重申您的问题。

    class BoxColliderV2 : public MyBaseCollider {
    public:
        void SetWidth(float new_width) override; 
    };
    

    您可能会发现这个video 很有用。兴趣点从大约 40 分钟开始(但如果可以的话,请观看整个视频)。如果您对建议感兴趣,我建议从std::variant 开始,如果可行,请转到虚函数。碰撞检测之类的问题很快就会变得非常复杂,几乎可以肯定在某个阶段你会需要double dispatch。从简单开始,因为它只会变得更复杂。

    这些来自 ISO 指南的摘录可能会有所帮助
    1.当你改变一个操作符的语义时,你做它 其他程序员更难理解你的代码。 guideline.
    2.动态铸造冗长难看,但故意如此,因为动态铸造是危险的,应该脱颖而出。 guideline

    【讨论】:

      【解决方案3】:

      我认为您从错误的方向解决问题。接口的目的是您不必知道确切的类型或实现。

      例如:您正在使用 Axis-Aligned Bounding Boxes 进行碰撞检测。因此,即使您的CircleCollider 使用radius,您仍然可以从中计算出它的widthheight。现在,您不必担心是在处理BoxCollider 还是CircleCollider,您拥有制作边界框的一切。

      class Collider
      {
      public:
          virtual float x() const = 0;
          virtual float y() const = 0;
          virtual float width() const = 0;
          virtual float height() const = 0;
      };
      
      class BoxCollider : public Collider
      {
          // Implementation...
      };
      
      class CircleCollider : public Collider
      {
          // Implementation...
      };
      

      当然,您可能正在使用其他东西,而不是 AABB。我只是想演示如何有效地使用接口。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-03-25
        • 1970-01-01
        • 1970-01-01
        • 2016-01-05
        • 2017-09-30
        • 1970-01-01
        • 2013-10-07
        相关资源
        最近更新 更多