【问题标题】:Is there a way to infer the type of an object?有没有办法推断对象的类型?
【发布时间】:2012-12-27 14:55:08
【问题描述】:

这可能是一个愚蠢的问题,我怀疑我知道答案(否),因为我似乎在这里碰壁了。

假设我有一个从某个类派生的对象集合:

class BaseClass;
class DerivedA: public BaseClass;
class DerivedB: public BaseClass;
class DerivedC: public BaseClass;
std::vector<BaseClass> myCollection;

我想根据具体类的类型调用一个方法:

class Processor {
  void doSomething(DerivedA a, DerivedB b);
  void doSomething(DerivedA a, DerivedC c);
}

问题是,如果我访问集合中的各个项目并尝试调用“处理器”中的“doSomething”方法,它将无法决定使用哪种方法(afaik)。所以我的问题是:有什么方法可以使用正确的派生类型获取集合中的项目?

【问题讨论】:

  • 如果你想要派生支持(以及随之而来的多态性)。你的向量应该是指针(和智能指针);不是基类实例。
  • 您可能还对visitor pattern感兴趣
  • @WhozCraig 感谢您的提醒,Karthik T 解释了为什么我需要这个,但我没有意识到如果我使用实例会发生对象切片。
  • 以下是 C++11 中的多分派示例:ideone.com/lTsc7M

标签: c++ dispatch


【解决方案1】:

如果您要保持doSomething 方法不变,这就是所谓的multiple dispatch,目前C++ 不支持。

如果它是 BaseClass 的虚成员函数,那么是的,它将是在调用它的对象上运行的 Mill C++ 多态性,但它仍然不会自动推断争论的类型。

要解决这个问题,您可以执行之前链接中建议的操作

void collideWith(Thing& other) {
     // dynamic_cast to a pointer type returns NULL if the cast fails
     // (dynamic_cast to a reference type would throw an exception on failure)
     if (Asteroid* asteroid = dynamic_cast<Asteroid*>(&other)) {
         // handle Asteroid-Asteroid collision
     } else if (Spaceship* spaceship = dynamic_cast<Spaceship*>(&other)) {
         // handle Asteroid-Spaceship collision
     } else {
         // default collision handling here
     }
 }

基本上保持转换为各种可能的派生类,直到其中一个工作并适当地调用其中一种方法(无需特别努力,因为编译器知道您要转换为哪种类型)。

重要提示:正如@WhozCraig 指出的那样,您的向量需要保存指针以避免Object-Slicing 并使整个问题变得毫无意义。

【讨论】:

  • 它没有我想要的那么漂亮,但看起来这是做我想做的最好的选择。也感谢您解释@WhozCraig 的评论,直到我读到您的回答,我才意识到他为什么这么说:-)
  • @uorbe001 抱歉,我没有在该评论中特别详细地阐述,但 Karthik 很好地填补了我在这个答案中留下的漏洞。很高兴你看到它是如何联系在一起的。
【解决方案2】:

好的,是的,您应该如上所述使用多态性。如果你的函数需要处理 2 个对象,尽管它变得非常复杂。

如果派生形成一个有限的集合并且彼此了解,您可以使用双重调度。它并不完美,但它解决了这种特殊情况。

class DerivedA;
class DerivedB;
class DerivedC;

class BaseClass
{
 public:
     virtual ~BaseClass();

     virtual void doSomethingWithBase( BaseClass & b2 ) = 0;
     virtual void doSomethingWithDerivedA( DerivedA & da ) = 0;
     virtual void doSomethingWithDerivedB( DerivedB & db ) = 0;
     virtual void doSomethingWithDerivedC( DerivedC & dc ) = 0;
};

class DerivedA : public BaseClass
{
   public:

      void doSomethingWithBase( BaseClass & b2 )
      {
           b2.doSomethingWithDerivedA( *this );
      }

      void doSomethingWithDerivedA( DerivedA & da )
      {
           // implement for two DerivedA objects
      }

      void doSomethingWithDerivedB( DerivedB & db )
      {
           // implement for an A and B
      }

      void doSomethingWithDerivedC( DerivedC & dc )
      {
          // implement for an A and C
      }
 };

 // implement DerivedB to call doSomethingWithDerivedB on its parameter
 // implement DerivedC to call doSomethingWithDerivedC on its parameter.

你明白了。从你打电话的地方,你不需要知道你有哪两种类型,你也不需要实际查找。但是,如果您添加了更多实现,您需要编辑大量代码,并且可能会考虑使用某种查找表。

如果您需要一个类来定义自己,您可以使用某种虚拟 id。

  class BaseClass
  {
      public:
         virtual int id() const = 0;
  };

然后你让类显示它们的 id 并根据这些 id 在表中找到处理这两个对象的处理程序。 id 不必是整数,它们可以是字符串,这样更容易避免命名冲突,这比不知道其派生类或它们彼此认识的基类的双重调度方法具有优势,并且是可扩展的。您也不必处理每一对。

【讨论】:

  • 我确实考虑过双重分派,但如果我想将逻辑保留在单独的类中(在这种情况下,我愿意),它就行不通。理论上,据我所知,您可以实现三重调度,但这会带来比其价值更多的麻烦:-(
猜你喜欢
  • 1970-01-01
  • 2017-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多