wec*_*ing 6 c++ polymorphism inheritance
我的程序需要处理不同类型的"音符": NoteShort,NoteLong...不同种类的票据应显示在以不同的方式GUI.我定义了这些笔记的基类,称为NoteBase.
我将这些笔记存储在XML中; 我有一个类从XML文件读取并存储笔记的数据vector<NoteBase *> list.然后我发现我不能得到他们自己的类型,因为他们已经转换为NoteBase *!
虽然if(dynamic_cast<NoteLong *>(ptr) != NULL) {...}可行,但实在太难看了.实现函数take NoteShort *或NoteLong *as参数不起作用.那么,有什么好方法可以解决这个问题吗?
更新:谢谢各位回复.我认为它不应该发生 - 但它确实发生了.我以另一种方式实现它,它现在正在工作.但是,据我记得,我确实声明了(纯)虚函数NoteBase,但忘了在派生类的头文件中再次声明它.我猜这就是造成这个问题的原因.
更新2(重要):我从C++ Primer中找到了这个引用,这可能对其他人有帮助:
有时更令人惊讶的是,即使基本指针或引用实际绑定到派生对象,对从base转换为派生的限制仍然存在:
Run Code Online (Sandbox Code Playgroud)Bulk_item bulk; Item_base *itemP = &bulk; // ok: dynamic type is Bulk_item Bulk_item *bulkP = itemP; // error: can't convert base to derived编译器无法在编译时知道特定转换在运行时实际上是安全的.编译器仅查看指针或引用的静态类型,以确定转换是否合法.在我们知道从base到derived的转换是安全的情况下,我们可以使用static_cast(第5.12.4节,第183页)来覆盖编译器.或者,我们可以使用dynamic_cast请求在运行时检查的转换,这将在第18.2.1节(第773页)中介绍.
这里有两个重要的思路和代码,首先是最短的:
您可能不需要重新投票.如果所有人都Note提供统一的行动(比方说Chime),那么你可以简单地:
class INote
{
virtual void Chime() = 0;
};
...
for_each(INote * note in m_Notes)
{
note->Chime();
}
Run Code Online (Sandbox Code Playgroud)
并且每个Note都Chime应该使用内部信息(例如持续时间和音调).
这很干净,简单,只需要很少的代码.但它确实意味着所有类型都必须提供并继承自特定的已知接口/类.
现在,当您确实需要知道类型并将其转换回来时,会出现更长且涉及更多的方法.有两种主要方法,一种可以与#3一起使用或组合的变体(#2):
这可以在带有RTTI的编译器(运行时类型信息)中完成,使其能够安全地dynamic_cast了解所允许的内容.但是,这仅适用于单个编译器和单个模块(DLL/SO/etc).如果您的编译器支持它并且RTTI没有明显的缺点,那么它是迄今为止最简单的并且在您的最终工作量最少.但是,它不允许类型识别自身(尽管typeof可以使用某个功能).
这是按照您的方式完成的:
NewType * obj = dynamic_cast<NewType*>(obj_oldType);
Run Code Online (Sandbox Code Playgroud)要使其完全独立,向基类/接口添加虚拟方法(例如Uuid GetType() const;)允许对象随时标识自身.这比第三种(真正的COM)方法有一个好处,也是一个缺点:它允许对象的用户做出明智的,也许更快的决定做什么,但需要a)他们演员(可能需要和不安全reinterpret_cast或C风格演员)和b)该类型不能进行任何内部转换或检查.
ClassID id = obj->GetType();
if (id == ID_Note_Long)
NoteLong * note = (NoteLong*)obj;
...
Run Code Online (Sandbox Code Playgroud)COM使用的选项是提供表单的方法RESULT /* success */ CastTo(const Uuid & type, void ** ppDestination);.这允许类型a)检查内部铸件的安全性,b)由内部自行决定执行铸造(有关于可以做什么的规则)和c)如果铸造不可能或失败则提供错误.但是,它a)阻止用户表单优化,b)可能需要多次调用才能找到成功的类型.
NoteLong * note = nullptr;
if (obj->GetAs(ID_Note_Long, ¬e))
...
Run Code Online (Sandbox Code Playgroud)以某种方式组合后两种方法(如果nullptr传递00-00-00-0000 Uuid和目的地,例如用类型自己的Uuid填充Uuid)可能是识别和安全转换类型的最佳方法.后两种方法和它们相结合,都是编译器和API独立的,甚至可以小心地实现语言独立性(正如COM那样,以合格的方式).
ClassID id = ClassID::Null;
obj->GetAs(id, nullptr);
if (id == ID_Note_Long)
NoteLong * note;
obj->GetAs(ID_Note_Long, ¬e);
...
Run Code Online (Sandbox Code Playgroud)
当类型几乎完全未知时,后两者特别有用:源库,编译器甚至语言都不是提前知道的,唯一可用的信息是提供给定的接口.使用这些小数据并且无法使用高度特定于编译器的功能(如RTTI),需要对象提供有关其自身的基本信息.然后,用户可以根据需要请求对象自我投射,并且对象可以完全自行决定如何处理.这通常与大量虚拟类甚至接口(纯虚拟)一起使用,因为这可能是用户代码可能具有的所有知识.
在您的范围内,此方法可能对您没有用,但可能是有意义的,并且对于类型如何标识自身并从基类或接口"向上"转换而言当然很重要.