zoukankan      html  css  js  c++  java
  • C++纯虚函数调用

        阅读本文之前,读者需要掌握 C++ 虚函数的基本用法,以及了解 C++ 的虚函数是怎么实现的,此为基础内容,不在本文的讨论范围。
        在上次实习生面试中,面试官了我C++虚函数是怎样实现的问题。我想读过 Inside the C++ Object Model 这本书的人对这点都是比较熟悉的,在解释过程中,他又问了我纯虚函数是什么,用来做什么。我在回答的过程中简单提了下“C++ 的纯虚函数在特殊情况下是有可能会被调用的,具体的行为由 C++ 的标准库的实现决定”,后来回想起这句话,想了好久没想到具体的被调用的情况,幸好面试官没追问这个问题,否则我真得语塞了(当时几乎整个过程都是我在滔滔不绝的回答,面试官就一直嗯嗯嗯的状态)。趁现在比较闲又不想复习考试,就顺便写写代码,针对这个问题总结出一篇博客文章与大家交流。
        首先,必须清楚的是纯虚函数本身是不应该被调用的!因为纯虚函数是用来定义接口的,有时候基类自己找不到一个合情合理的实现,所以用虚函数的形式声明,让他的子类去做具体的实现。因此,如果纯虚函数被调用了,那一定是你的程序里出现了逻辑上的错误,这是我们在工作中需要了解和避免的,这也就是本文讨论的目的之一啦。
        你知道,虚函数是通过指针或者引用来调用的,调用的具体函数由指针/引用的实际决定。而这个实际的对象,是可以由这个对象的内存块中的第一个值,vptr,指向虚函数表的指针来确定的。纯虚函数是属于基类的,所以要调用纯虚函数,这个指针所指的对象必须是基类。但是呢,抽象类(包含了纯虚函数的类)的对象是不允许被用户定义的,唔,这个规定看似严谨,C++ 怎么可能让你去调用纯虚函数,看本文的你也在好奇这个问题吧。不允许用户定义抽象类的对象,是的,不代表这种对象不能被构造!记得对象的构造过程,是先调用基类的构造函数,再调用子类的构造函数,也就是先构造基类对象,再构造子类对象,对象的析构我就不提了哈。也就是说,在基类的构造函数里调用的任何虚函数,都是调用基类自己的虚函数,而不是子类的虚函数,噢!漏洞就在这里!

        当然,如果你尝试写下这样的代码:

    class Base{
    public:
        virtual void foo()=0;
        Base()   { foo();  }    // 调用纯虚函数
    };
    class Derived: Base{
        void foo() {  }
    };
    int main() {
        Derived d;
    }
        很幸运的,编译器能发现错误并向你吐槽,以下是我使用CodeBlocks(自带MinGw的,含基于gcc 4.7.1的编译器)编译得到的一个警告和一个链接错误:
    编译警告:warning: pure virtual 'virtual void Base::foo()' called from constructor
    链接错误:undefined reference to `Base::foo()'
        但是很不幸的,实际的应用中代码往往复杂得多,使得编译器无法在编译的时候发现问题。修改上面的代码如下,就可以成功的调用到虚函数了:
    class Base{
    public:
        virtual void foo()=0;
        Base() { call_foo();}
        void call_foo() { foo(); }

    };
     
    class Derived: Base{
        void foo() {  }
    };
     
    int main() {
        Derived d;
    }
    运行后得到的结果为:

     

        哈哈好奇心终于得到了满足了!总结起来,其实还是 "Item 9: Never call virtual functions during construction or destruction." ( The third edition of Scott Meyers' popular book, Effective C++).
        最后顺便提一下,在 C++ 11 的标准文档【ISO/IEC 14882:2011(E)】中,也有相关的描述:
    (10.4.6)" Member functions can be called from a constructor (or destructor) of an abstract class; the effect of making a virtual call (10.3) to a pure virtual function directly or indirectly for the object being created (or destroyed) from such a constructor (or destructor) is undefined."
        也就是说,纯虚函数调用的行为是未定义的,而 gcc 的默认实现则是终止程序,输出相关的信息。
    参考链接:
    参考书籍:
    Effective C++, 3rd Edition, Scott Meyers
    ISO/IEC 14882:2011(E)


    Pony279原创博文,转载请注明出处 http://www.cnblogs.com/Pony279/archive/2013/06/04/3117955.html 

  • 相关阅读:
    ASP.NET把客户机IP转换成真实地址(利用纯真 IP 数据库)
    关于分层走班教学的思考
    asp.net jQuery Ajax用户登录功能的实现
    C#判断网络地址 Url 是否存在的方法
    Asp.net获得远程网页源代码
    jQuery Ajax 调用aspx后台方法返回DataTable 的例子
    ASP.NET2.0中将GridView导出为Excel
    利用DataGrid显示某目录下的所有文件
    C#使用存储过程详细
    javascript获取当前日期时间
  • 原文地址:https://www.cnblogs.com/Pony279/p/3117955.html
Copyright © 2011-2022 走看看