C++编程思想学习——多态性与虚函数

1、  虚函数与向上类型转换

使用向上类型转换时,将派生类的对象传递给一个基类的对象,用转换后基类的对象调

用相应的成员函数时,调用的是基类的成员函数,而并非派生类对象的成员函数。但是,程序在运行时,我们经常希望无论什么时候一个类的对象都是调用的本类的成员函数而不是基类的成员函数。所以,为了达到这一目的,C++中采用“晚捆绑”技术,即在程序运行时才将函数体与函数调用想联系起来,而不是过程型语言中的“早捆绑”:捆绑在程序运行之前由编译器进行捆绑。

       而,要使用晚捆绑在c++中使用关键字vitual,这个技术只在使用含有vitual函数的基类的地址时才起作用。虽然,虚函数很重要,但是,由于使用虚函数会有额外的开销(速度问题),所以,C++设计为可选(通过virtual进行选择)。但是,程序设计时,作为一个好的程序员应该考虑的问题是程序的瓶颈方面,而不是在猜测方面下功夫。所以,如果为了程序的效率考虑的话,只需要不适用虚函数的函数即可。

2、  C++中晚捆绑的实现

为了实现晚捆绑,编译器需要进行一定的动作。多数的编译器通过如下实现:编译器为

每一个包含虚函数的类新建了一个VTABLE,这个表中的每一项为指向这个类的一个虚成员函数的函数入口地址。并在每个包含虚函数的类的对象中存放了一个指向本类所对应的VTABLE表的地址的指针。接下来,在编译器编译时,如果遇到一个用基类指针做虚函数调用时,编译器自动将能够找到该对象的类所对应的VPTR,并在VTABLE中查找函数地址的代码。

       在C++中,成员函数的调用时除了将函数参数表中所定义的参数的同时,也将本对象的this指针也默认传到了函数体。所以,成员函数知道它工作在那个特殊的对象上。

3、  虚函数的遭捆绑

如果,编译器清楚的知道它所使用对象的确切类型,则可以使用遭捆绑技术。如,基类

对象调用基类的虚函数时。

4、  抽象基类和纯虚函数

如果,仅仅希望基类给派生类提供一个公共的接口,而不希望实际的创建一个基类的对

象时,我们可以在基类中定义一个纯虚函数来实现这一功能。若某个类中含有一个纯虚函数,则本类为抽象类,抽象类不能实例化式的创建它的一个对象。

       使用virtual关键字,并在函数末尾加=0来定义一个纯函数,也即定义了一个抽象类。

Note:

       纯虚函数禁止对象使用值传递方式调用,这样即防止了对象切片,也保证了向上类型转换期间总是使用指针或者引用。

5、纯虚函数——基类代码的重用

       虽然,编译器不允许生成抽象基类的对象,但是,在基类中对纯虚函数提供定义是可能的。首先,在类的声明的外部,以全局变量范围的形式,定义这个虚函数。然后,在抽象基类的派生类中可以通过:“类名::抽象基类的虚函数名()”的方法来调用抽象基类的虚函数的定义的函数体,这样就实现了,对于基类的一段公共代码的重用。

6、继承和虚函数的VTABLE

针对指向基类对象的指针的处理时编译器只能把它作为基类对象处理,既是它指的是一

个派生类的对象,因为编译器这时是无法确定该指针具体是指向那个派生类的对象的。在VTABLE中可能有,也可能没有一些其他函数的地址,但是,无论怎样,对这个VTABLE地址做虚函数调用时就可能调用了该类所没有的函数,这不是我们想要的,所以,在向上转换的函数中,编译器不允许我们对只存在派生类中的函数做虚函数调用。

       构造函数不能为虚函数,而析构函数必须为虚函数,这样可以保证对象的正确的生成,同时保证向上类型转化后的对象能够被正确的析构,而不至于发生内存泄露。

7、  对象切片

在使用虚函数时,如果,用按值传递的方式来向上转换,会发生对象切片。即,基类的

拷贝构造函数被调用,拷贝构造函数初始化VPTR(虚函数入口地址表指针),并将只属于基类对象的部分拷贝,因此派生类的对象在此过程中真的变成了一个基类的对象。但是,如果将基类的虚函数变为纯虚函数时,按值传递方式的向上类型转换将被编译器禁止,因为,需要调用拷贝构造函数来生成一个基类的对象,而抽象类是不允许生成对象的。

8、  重载和重新定义

C++中允许虚函数的重载,但是在派生类中如果重写了基类的虚函数,则不允许改变重

定义过的函数的返回值(如果,不是虚函数则允许),从而保证了向上类型转变后进行虚函数调用出现返回值与派生类不同的问题。

       另外,如果这个返回值被改变为原返回类型的派生类型也是允许的,这样也同样遵循了合约,同时,返回了对象所属类的确切类型,这常用的,但返回基类类型通常会解决我们的问题,所以,也是一个特殊的功能。

9、  内联的构造函数

内联的构造函数会降低函数调用的代价,但是,虚函数的构造函数设计为内联时可能会

出现以下情况:

       编译器会将一些隐藏代码插入到构造函数中,包括:初始化VPTR,检查this指针,调用基类的构造函数。所以,我们在设计抽象类时要考虑是否要把构造函数去掉内联性。

10、             虚函数在构造函数中的行为

由于构造函数中可能只是部分形成对象——我们只知道基类被初始化但哪个类将会从

这个基类继承而来是不知道的。而虚函数在继承层次上是“向前”和“向外“进行调用的,他可以调用派生类中的函数,但是,如果我们在构造函数中如果也这样做就会出现问题,因为在构造函数中调用的函数中操作的成员可能还没有生成呢。

       所以,C++规定构造函数中的虚函数的调用,调用的只是虚函数的本地版本。另外,这也实现了,在类层次的构造函数中对于VPTR的从基类到派生类依次处理。

12、纯虚析构函数

基类中必须为纯虚析构函数提供一个函数体,用来析构是能够正确的从派生类到基类的

析构,但是,当从某个含有析构函数的类中继承出一个类时,并不要求派生类提供纯虚函数的定义,因为,编译器会自动的生成析构函数,从而实现对析构函数的定义。这样虚析构函数和纯虚析构函数的唯一区别就是阻止基类的实例化。

      所以,通常如果我们类中要有一个虚函数,那么我们立即增加一个虚析构函数,从而保证程序的鲁棒性。

13、析构函数中的虚机制

如果在不同函数中调用虚函数,则编译器会使用“晚绑定”的方式调用该函数。但是,

如果是在析构函数中调用虚函数,则,虚机制被忽略,只调用此虚函数的本地版本。因为,所调用的这个虚函数可能是本析构函数的派生类的成员函数。而,由析构的由外向内(基类)性,此时,它的派生类的对象已经被析构了,所以,会产生错误。

14、单根继承(基于对象的继承)

当我们需要使多个不同类的对象具有同样的操作时,我们可以先创建一个非常简单的只

包括纯虚析构函数的基类,然后,这些不同的类都从这个基类继承而产生,最后,在基类上添加该操作,即实现了该功能,但是,由于未必所有的类都是新生成的,有一些类时编译器已经定义好的,这样为了使这样的类的对象也能有这样的操作,就需要多重继承,但是,多重继承会相当复杂,解决这一问题模版类提供了很好的技术路线。

15、运算符重载

由于Virtual的基类提供接口,执行时调用具体的相应的派生类的操作的性质,我门可

以将virtual用于运算符重载的实现上,主要为处理数学部分,如:矩阵、向量和标量的运算上。但是由于,但一个虚函数只能进行单一指派——即,只能判断一个未知对象的类型。所以,如果重载的运算符有两个或两个以上的向上类型转换,这样具体调用虚函数时需要两个或两个以上的对象指派。这时就需要使用多重指派技术,即在一个虚函数中调用虚函数,引起第二个虚函数的调用。当最后一个虚函数被调用时,已经得到了每一个对象的类型。

16、向下类型转换

在揭开typeid神秘面纱之前,我们先来了解一下RTTI(Run-TimeType Identification,

运行时类型识别),它使程序能够获取由基指针或引用所指向的对象的实际派生类型,即允许“用指向基类的指针或引用来操作对象”的程序能够获取到“这些指针或引用所指对象”的实际派生类型。在C++中,为了支持RTTI提供了两个操作符:dynamic_cast和typeid。
    dynamic_cast允许运行时刻进行类型转换,从而使程序能够在一个类层次结构中安全地转化类型,与之相对应的还有一个非安全的转换操作符static_cast。

Dynamic_cast用于显示类型转换,当使用它时仅当类型转换是正确的并是成功的时,

返回值是一个指向所需类型的指针,否则它将返回0来表示这并不是正确的类型。Static_cast,dynamic_cast都不允许类型转换到该类层次的外面。但,static_cast静态的浏览类层次总是有风险的。所以,一般使用dynamic_cast。

      本文为个人学习C++编程思想时个人心得体会,所以,难免有不严谨之处,甚至有可能是错误的理解。如发现错误,或者疑问。欢迎提出讨论,共同学习、进步。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值