【问题标题】:Passing shared_ptr<Base> as shared_ptr<Derived>将 shared_ptr<Base> 作为 shared_ptr<Derived> 传递
【发布时间】:2019-03-03 12:16:17
【问题描述】:

我目前有以下结构

class A

class B : public A

class C : public A

我在AB 中定义了虚拟方法,C 正在覆盖它们。方法是这样的

bool C::CheckCollision(shared_ptr&lt;B&gt; box);

bool B::CheckCollision(shared_ptr&lt;C&gt; triangle);

我还有一个shared_ptr&lt;A&gt; 的向量,其中存储了所有游戏对象。问题是我无法执行以下操作

for (int i = 0; i < objects.size(); i++)
{
    for (int j=i; j < objects.size(); j++
    {
        objects[i]->CheckCollision(objects[j]);
    }

}

我收到一条错误消息,指出参数列表与重载函数不匹配。这是有道理的,因为我试图通过shared_ptr&lt;A&gt; 我期待shared_ptr&lt;B&gt;shared_ptr&lt;C&gt;,但我该如何解决这个问题?还有其他方法吗?

【问题讨论】:

  • 你不能绕过这个。如果CheckCollision() 使用了仅在BC 中可用的东西,并且您将传递一个指向A 对象的std::shared_ptr&lt;A&gt; 怎么办?您需要重新考虑您的设计。
  • 有道理,我想到的其他事情是我可以使用枚举来存储类型信息并执行虚拟 GetType 函数。我知道你并不了解整个情况,但这听起来很糟糕吗?
  • 对我来说,enum(最好是enum class)持有一个类型的C 味道太浓了,但有时它是正确的工具。如果你真的想要多态,你需要坚持virtualsomewhere。请注意,可以使用 dynamic_cast 进行类型检查,但这很少是理想的解决方案(不仅速度慢,而且会给代码增加很多噪音)。
  • 我认为CheckCollision 应该属于A,然后它会调用虚函数——比如BoundingBox()——来获取运行碰撞检测算法所需的信息。跨度>
  • 你有 virtual CheckCollision(shared_ptr&lt;B&gt; box)virtual CheckCollision(shared_ptr&lt;C&gt; box),在你的 A 类中声明?因为如果是这样,那么设计肯定会被破坏。 A 不应该知道 BC。如果你在A 中没有virtual CheckCollision(shared_ptr&lt;B&gt; box)virtual CheckCollision(shared_ptr&lt;C&gt; box),那么你就没有你覆盖的函数。

标签: c++ inheritance polymorphism shared-ptr double-dispatch


【解决方案1】:

让我们让它与虚函数和指向基类的共享指针一起工作

首先,您可以完美地使多态性工作,使用指向基的共享指针。这里有一个小 sn-p 向您展示如何做到这一点:

class A {
public: 
    virtual void show() { cout<<"A"<<endl; } 
    virtual void collide(shared_ptr<A> a) { cout<<"collide A with "; a->show();  } 
    virtual ~A() {}
};

class B : public A {
public:
    void show() override { cout<<"B"<<endl; } 
    void collide(shared_ptr<A> a) override { cout<<"collide B with "; a->show();  } 
};

class C : public A {
public:
    void show() override { cout<<"C"<<endl; } 
    void collide(shared_ptr<A> a) override { cout<<"collide C with "; a->show();  } 
};

你的双循环看起来像:

vector<shared_ptr<A>> objects; 
objects.push_back (make_shared<A>());   // populate for the sake of demo
objects.push_back (make_shared<B>()); 
objects.push_back (make_shared<C>()); 

for (int i = 0; i < objects.size(); i++)
{
    objects[i]->show(); 
    for (int j=i; j < objects.size(); j++)
    {
        objects[i]->collide(objects[j]);   // note that you have to use -> not .
    }
}

现在,您知道,为了掌握这些组合,我使用了一个覆盖,它知道它自己的对象的真实类型,但不知道任何关于伙伴对象的真实类型的具体信息。所以要弄清楚它是哪种配对,我需要调用伙伴对象的多态函数。

Online demo

解决问题的更一般的方法是双重调度

这个小的概念证明是为了展示一个简单的例子。理想的情况是问题可以分解为每个合作伙伴对象完成问题的一部分。但事情并不总是那么简单,因此您可以通过谷歌搜索 double dispatch 找到更复杂的技术。幸运的是,碰撞的例子很常见。

这里another demo 使用了覆盖和重载的组合。我认为这是您尝试实现的那种事情,但通过更多的间接级别来解决它。它是inspired by the visitor pattern:对象的多态碰撞函数是用一个指向伙伴对象基类的共享指针调用的。但是这个函数的实现会立即调用伙伴对象的多态函数,并以对自身的引用作为参数(即参数的真实类型的知识,允许编译器选择正确的重载)。不幸的是,这种“反弹”方法(顺便说一下,它是一种倒置的访问者)需要基类知道其所有潜在的派生类,这远非理想。但它允许为每种可能的组合提供不同的行为。

双重分派的另一种方法是使用dispatch table。这通过管理一种具有两种类型的虚拟表来工作,并进行一些查找以调用正确的函数以获得正确的组合。

【讨论】:

  • 谢谢你的解释和链接,我看看他们:)
猜你喜欢
  • 2012-11-04
  • 2017-08-06
  • 2010-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-21
相关资源
最近更新 更多