« 视锥类CFrustum .zwqxin ver乱弹OpenGL选择-拾取机制(上) »

子类调用父类的纯虚函数之问题

今天遇上的一点C++问题,记录下来备忘一下。父类是个抽象类,子类却对它有些特别的需求——子类调用父类的纯虚函数。(什么?大逆不道?)——ZwqXin.com

本文来源于 ZwqXin (http://www.zwqxin.com/), 转载请注明
      原文地址:http://www.zwqxin.com/archives/cpp/pure-virtual-between-derivation.html

有这么一个库,库里有个工具类,不妨认为是一个关于listbox操作的类吧(事实上就是了- -)。它通过头文件提供接口给我们使用,而把实现藏进dll里面了(或者是lib里面)。这个类有个特点:除了构造析构函数之外的都是纯虚函数。也就是说,这完全就是一个“接口(interface)”概念的类。

纯虚函数的概念就不多解释了。它的代名词就是“抽象”,尽管你不一定认为它有多么需要抽象。

现在,我觉得这个工具类(姑且叫BaseListBox)不够用,想从中派生出一个更有内涵的子类(不妨叫DerivedListBox)。问题来了,这不是MFC,没有像MFC那样预先考虑让人随便重载界面类。面对这个完全“虚”了的BaseListBox,我能想到的重载方式——没啥,把这些函数全部“重载”吧。

  1. class BaseListBox
  2. {
  3. public:
  4.    BaseListBox(){}
  5.  
  6.    virtual void func1()=0;
  7.    virtual void func2()=0;
  8. };
  9.  
  10. class DerivedListBox : public BaseListBox
  11. {
  12. public:
  13.    DerivedListBox() : BaseListBox(){}
  14.  
  15.   void NewFunc(){} 
  16.  
  17.    virtual void func1(){};
  18.    virtual void func2(){};
  19. }

实际上该工具类也没成员函数,因此用的是默认析构(因为它也是继承来的,这个与本主题无关,就不待多言了,但继承体系的底部有个虚函数析构是时刻有必要提醒的)。 ~简化一下,用上面的代码说明问题好了。当然,事实上这种funX()的函数有N个....

如何维护“子类就是父类”这个说法呢?在代码部分变通一下:

  1. class DerivedListBox  :  public BaseListBox
  2. public: 
  3.    DerivedListBox() : BaseListBox(){} 
  4.  
  5.   void NewFunc(){}  
  6.   
  7.    virtual void func1(){ return BaseListBox::func1() }; 
  8.    virtual void func2(){ return BaseListBox::func2() }; 
  9. }

啥米?调用父类的纯虚函数?!那不是纯虚函数吗?那不是接口吗?呃....没什么好惊奇的。原来的类能给你用,就说明了这些接口真的有代码实现部分。(至于原来是怎么被用的,自个儿想想猜猜吧。)不过这段实现不能用于继承体系,注定了与动态调用无缘。在这里funX函数通过静态方式调用了父类的同名纯虚函数,驶离了“无法实例化”的魔咒。BaseListBox作为“限定符”意味着“要调用的的确就是BaseListBox的那个funcX”,如同通过场景唯一的ID访问场景节点对象[访问游戏怪物],没有什么可以作为意外的。

所以,注意,C++里纯虚函数也可以有默认实现!

但是,VS2005里抛出了这种链接错误:
 error LNK2001: 无法解析的外部符号 "public: virtual void__thiscall BaseListBox::func1() "
(?.......@@UBEPB_WI@Z).........fatal error LNK1120: 1 个无法解析的外部命令
——ZwqXin.com

改为((BaseListBox*)this)->func1()倒是过了链接,但是同样是运行终止的。

无理解错的话,应该是因为编译器找不到该BaseListBox::func1()的实现(默认纯虚实现部分)。链接DLL什么的我真是云里雾里,不过反正就是“无法找到”(或者说“不认为找到了”更合适)。我简单地做过实验,如果BaseListBox的实现是确实在一个实现文件(.cpp)里的话,确实上面这样的代码是能够编译和运行的。

[C++语言相关]为什么需要工厂(Factory)一文中,根据作者所言,大概也知道在类库里面接口和实现有可能不是那么简单地相连的,编译器说“不认识”自然有它的苦衷,我不认为这次也是因为它坏掉了。(好心人可详细解释一下吗?)

{

更:在此引用评论中linzhehui同学的意见,以思疑上述代码:

 

错到头了。。

你这个最后成了 selfcall 了,,最终肯定 stack 溢出。子类父类虚函数的多态是通过需函数表来实现的,

virtual void func1(){ return BaseListBox::func1() }; 

这个代码完全是无意义的。。实质上等同于 

virtual void func1(){ return func1() }; 

}

因此,有个折中的坏主意:

  1. class BaseListBox 
  2. public: 
  3.    BaseListBox(){} 
  4.  
  5.    virtual void func1()=0; 
  6.    virtual void func2()=0; 
  7. }; 
  8.  
  9. class DerivedListBox : public BaseListBox 
  10. public: 
  11.    DerivedListBox(BaseListBox  *listbox)
  12.      : baseListbox(listbox),  BaseListBox(){} 
  13.  
  14.   void NewFunc(){}  
  15.   
  16.    virtual void func1(){ baseListbox-> func1() }; 
  17.    virtual void func2(){ baseListbox-> func2() }; 
  18.  
  19. private:
  20.    BaseListBox  *baseListbox;
  21. }

这种手法的重点应该落在baseListbox这个成员指针的“值”之问题上。的确,库里面BaseListBox本来就不是为了作为基类而使用的(或者我改回IListBox更不容易招见误会),库通过其他手段能直接创建一个真正的ListBox界面控件,返回其BaseListBox*指针。(或许前面的错误与此也有关。)这样,通过DerivedListBox的构造函数传入此指针后,就如同DerivedListBox 作为一个可装单一物件的容器与BaseListBox关联。这样一来,其实继承也没什么用处了。

或许改名IListBoxContainer更适合,但是我仍然想让该DerivedListBox像一个真正的“Derived-ListBox ”,于是把BaseListBox里面的纯虚函数都通过这种“呼叫”的方式,在每个同名函数里通过BaseListBox*指针调用。不抹去“: public BaseListBox  ”也是为了让继承体系更底端的函数也“有效”。

  1. int main()
  2. {
  3. //必须有种能产生有效BaseListBox指针的方式
  4.  BaseListBox *olistbox = xx->createyy();
  5.  
  6.   DerivedListBox *listbox = new DerivedListBox (olistbox );
  7.  
  8.   listbox->func1();
  9.   listbox->func2();
  10.   listbox->NewFunc();
  11.  
  12. return false;
  13. }

总之,在一定场合这种坏主意还是能帮忙的。

感谢你的指导与收看,这里是ZwqXin3D编程事件记录簿,谢谢莅临,意见可在本页面评论或光临舍下的留言簿

阁下对此问题有更好的处理方式吗?可否不吝口水告之呢?

本文来源于 ZwqXin (http://www.zwqxin.com/), 转载请注明
      原文地址:http://www.zwqxin.com/archives/cpp/pure-virtual-between-derivation.html

  • quote 1.linzhehui
  • 错到头了。。
    你这个最后成了 selfcall 了,,最终肯定 stack 溢出。子类父类虚函数的多态是通过需函数表来实现的,
    virtual void func1(){ return BaseListBox::func1() };
    这个代码完全是无意义的。。实质上等同于
    virtual void func1(){ return func1() };




    zwqxin 于 2011-12-28 20:48:40 回复
    谢谢阁下指正啊。先后记一下,有空再重试一下。
  • 2011-12-28 15:42:24 回复该留言
  • quote 2.SSS
  • 不知道你是否还关注这个问题啊。不过看到了这文章,还是说说我的看法吧。
    先确认一下你的意图:
    你是希望把一个基类扩展一下,但是其原有的纯虚函数还是保持原有的状态是吗?
    其实方法没你想的那么复杂,只要不要实现这些纯虚函数就行了,让其保持是“虚”的。那么这个新的子类,在接口方面和原有的基类是一样的。
  • 2013-10-11 14:57:32 回复该留言

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

IE下本页面显示有问题?

→点击地址栏右侧【兼容视图】←

日历

Search

网站分类

最新评论及回复

最近发表

Powered By Z-Blog 1.8 Walle Build 100427

Copyright 2008-2013 ZwqXin. All Rights Reserved. Theme edited from ipati.