C++纯虚函数

[1] 纯虚函数相当于java中的接口.
[2] 带纯虚函数的类叫虚基类,这种基类不能直接生成对象,而只有被继承,并重写其虚函数后,才能使用。
     这样的类也叫抽象类。



阅读本文之前,读者需要掌握 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 的默认实现则是终止程序,输出相关的信息。

总结:
C++虚函数与纯虚函数用法与区别
1 含有纯虚函数的类被称为抽象类(abstract class),不能用来定义对象的。
2 虚函数可以被直接使用,也可以被子类(sub class)重载以后以多态的形式调用,
而纯虚函数必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class)只有声明而没有定义。
3 虚函数和纯虚函数通常存在于抽象基类(abstract base class -ABC)之中,被继承的子类重载,目的是提供一个统一的接口。
4 虚函数必须实现,如果不实现,编译器将报错,错误提示为:
error LNK****: unresolved external symbol "public: virtual void __thiscall
ClassName::virtualFunctionName(void)"
5 实现了纯虚函数的子类,该纯虚函数在子类中就变成了虚函数,子类的子类即孙子类可以覆盖
该虚函数,由多态方式调用的时候动态绑定。
6 虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。
7 多态性指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。
C++支持两种多态性:编译时多态性,运行 时多态性。
a.编译时多态性:通过重载函数实现
b 运行时多态性:通过虚函数实现。
8 如果一个基类的【析构被声明为虚析构函数】,则【它的派生类中的析构函数也是虚函数】


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值