【问题标题】:Conversion from subclass to superclass to subclass?从子类到超类再到子类的转换?
【发布时间】:2011-12-18 12:57:14
【问题描述】:

我的程序需要处理不同种类的“笔记”:NoteShortNoteLong... 不同种类的笔记应该以不同的方式显示在 GUI 中。我定义了这些笔记的基类,称为NoteBase

我将这些注释存储在 XML 中;我有一个从 XML 文件中读取数据并将笔记数据存储在vector<NoteBase *> list 中的类。然后我发现我无法获取自己的类型,因为它们已经转换为NoteBase *

虽然if(dynamic_cast<NoteLong *>(ptr) != NULL) {...} 可能有效,但它真的太丑了。实现函数以NoteShort *NoteLong * 作为参数不起作用。那么,有什么好办法解决这个问题呢?

更新:谢谢大家的回复。我认为这也不应该发生——但它确实发生了。我以另一种方式实现了它,现在它正在工作。但是,据我记得,我确实在NoteBase 中声明了(纯)虚函数,但忘记在派生类的标题中再次声明它。我想这就是导致问题的原因。

更新 2(重要): 我从 C++ Primer 中找到了这句话,可能对其他人有帮助:

有时更令人惊讶的是, 即使在基指针或 引用实际上绑定到派生对象:

 Bulk_item bulk;
 Item_base *itemP = &bulk;  // ok: dynamic type is Bulk_item
 Bulk_item *bulkP = itemP;  // error: can't convert base to derived

编译器无法在编译时知道特定的 转换在运行时实际上是安全的。编译器只看 在指针或引用的静态类型处确定是否 转换是合法的。在那些情况下,当我们知道转换 从基础到派生是安全的,我们可以使用 static_cast (Section 5.12.4,第183) 覆盖编译器。或者,我们可以请求一个在运行时检查的转换,方法是使用 dynamic_cast,在第 18.2.1 节(第 773 页)中有介绍。

【问题讨论】:

  • 这是一个非常离题的话题,只是出于好奇:还有什么其他原因你实际上需要有几个类来实现你的目标?
  • 当然因为它们有许多共同的属性,但有时应该区别对待。观看this 30 秒,你就会知道我现在在做什么......

标签: c++ inheritance polymorphism


【解决方案1】:

这里有两个重要的思路和代码,所以首先是最短的:


您可能不需要重新投射。如果所有Notes 都提供统一的操作(比如Chime),那么您可以简单地拥有:

class INote
{
    virtual void Chime() = 0;
};

...
for_each(INote * note in m_Notes)
{
    note->Chime();
}

每个Note 都将使用内部信息(例如,持续时间和音高)按应有的方式Chime

这是干净、简单的,并且需要最少的代码。但是,这确实意味着所有类型都必须提供并从特定的已知接口/类继承。


现在,当您确实需要知道类型并回溯到它时,就会出现更长、更复杂的方法。有两种主要方法,还有一种变体(#2),可以与#3一起使用或结合:

  1. 这可以在带有 RTTI(运行时类型信息)的编译器中完成,允许它安全地 dynamic_cast 并充分了解允许的内容。然而,这只适用于单个编译器,也可能是单个模块(DLL/SO/etc)。如果您的编译器支持它并且 RTTI 没有明显的缺点,那么它是迄今为止最简单的并且需要最少的工作量。但是,它不允许类型标识自己(尽管typeof 函数可能可用)。

    按照您的方式完成:

    NewType * obj = dynamic_cast<NewType*>(obj_oldType);
    
  2. 为了使其完全独立,向基类/接口添加虚拟方法(例如,Uuid GetType() const;)允许对象随时识别自己。与第三种(真实 COM)方法相比,这有一个好处,也有一个缺点:它允许对象的用户对要做什么做出明智且可能更快的决定,但需要 a) 他们投射(这可能需要和unsafe reinterpret_cast 或 C 风格转换)和 b)该类型不能进行任何内部转换或检查。

    ClassID id = obj->GetType();
    if (id == ID_Note_Long)
        NoteLong * note = (NoteLong*)obj;
        ...
    
  3. COM 使用的选项是提供RESULT /* success */ CastTo(const Uuid &amp; type, void ** ppDestination); 形式的方法。这允许类型 a) 在内部检查强制转换的安全性,b) 在内部自行决定执行强制转换(有关于可以做什么的规则)和 c) 如果强制转换不可能或失败,则提供错误。但是,它 a) 会阻止用户表单优化,并且 b) 可能需要多次调用才能找到成功的类型。

    NoteLong * note = nullptr;
    if (obj->GetAs(ID_Note_Long, &note))
        ...
    

以某种方式结合后两种方法(例如,如果传递了 00-00-00-0000 Uuid 和 nullptr 目标,则使用类型自己的 Uuid 填充 Uuid)可能是两者的最佳方法识别和安全地转换类型。后一种方法以及它们的组合都独立于编译器和 API,甚至可以小心地实现语言独立性(就像 COM 所做的那样,以合格的方式)。

ClassID id = ClassID::Null;
obj->GetAs(id, nullptr);
if (id == ID_Note_Long)
    NoteLong * note;
    obj->GetAs(ID_Note_Long, &note);
    ...

当类型几乎完全未知时,后两者特别有用:源库、编译器甚至语言都事先不知道,唯一可用的信息是提供了给定的接口。处理如此少的数据并且无法使用高度特定于编译器的功能(如 RTTI),要求对象提供有关其自身的基本信息是必要的。然后,用户可以要求对象根据需要进行自我转换,并且对象完全可以自行决定如何处理。这通常与高度虚拟的类甚至接口(纯虚拟)一起使用,因为这可能是用户代码可能拥有的所有知识。

在您的范围内,此方法可能对您没有用处,但可能会引起您的兴趣,并且对于类型如何识别自己并从基类或接口“向上”转换回来当然很重要。

【讨论】:

    【解决方案2】:

    使用多态访问每个派生类的不同实现,如下例所示。

    class NoteBase
    {
      public:
        virtual std::string read() = 0;
    };
    
    class NoteLong : public NoteBase
    {
      public:
        std::string read() override { return "note long"; }
    };
    
    class NoteShort : public NoteBase
    {
      public:
        std::string read() override { return "note short"; }
    };
    
    int main()
    {
      std::vector< NoteBase* > notes;
      for( int i=0; i<10; ++i )
      {
        if( i%2 )
          notes.push_back(new NoteLong() );
        else
          notes.push_back( new NoteShort() );
      }
    
      std::vector< NoteBase* >::iterator it;
      std::vector< NoteBase* >::iterator end = notes.end();
      for( it=notes.begin(); it != end; ++it )
        std::cout << (*it)->read() << std::endl;
    
      return 0;
    }
    

    【讨论】:

      【解决方案3】:

      正如其他人所指出的,您应该尝试以一种无需强制转换即可完成所有您需要的事情的方式来设计基类。如果这是不可能的(也就是说,如果您需要特定于子类的信息),您可以像以前一样使用强制转换,也可以使用双重调度。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-11-30
        • 2012-10-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-05-24
        相关资源
        最近更新 更多