当前位置:首页 > 芯闻号 > 充电吧
[导读]    多态性是C++的一个重要特征。从广义上说,多态性是指一段程序能够处理多种类型对象的能力;具体地讲,多态性就是对不同对象发出同样的指令时,不同对象会有不同的行为。    



    多态性是C++的一个重要特征。从广义上说,多态性是指一段程序能够处理多种类型对象的能力;具体地讲,多态性就是对不同对象发出同样的指令时,不同对象会有不同的行为。
    如果程序员充分利用C++的多态性,设计程序的运行方式会更加灵活多样,但是会带来一些暗藏的细节问题。这些细节的漏洞也许会通过编译,但是在某些情况下,不可预测的结果或者背离编程者初衷的结果都会导致程序变得混乱不堪,甚至产生较大的风险。为了规避这些风险,MISRA C++推荐了一些编程规则。这些规则能够帮助程序员更加完备或者完美地实现多态性,充分体现C++相比于传统C语言的一些优势。
    本文主要介绍两类在实现形式的多态性中需要注意的一些问题:一是运算符的重载,这是编译时的多态性,即程序在编译时就能根据重载的情况确定需要调用的函数;二是虚函数的使用,这是运行时的多态性,即在程序执行前,无法根据函数名和参数来确定调用哪个函数,必须在程序执行过程中,根据执行的具体情况来动态确定。


1 运算符的重载
    运算符重载就是定义某个运算符对于某个类的具体含义。通过运算符的重载,程序员可以针对一些特定的类型使用重载的运算符含义。
    规则5-2-11(强制):逗号(,),与(&&)以及或(||)运算符不允许被重载。


    如果getValue和setValue的返回类型使用重载运算符&&,则这两个函数都需要计算。
    C++的内部规定是,&&和||都是在已知结果的情况下不再计算后面的值,比如0&&(a--)&&(b++)。然而重载&&运算符和||运算符导致了程序运行时要计算所有的表达式。这对于一些使用&&做判断的运算来说,会导致一些错误。比如getchar()&&putchar(),在读取文件时,如果读到文件尾部,即得到getchar()为0时,就不需要再执行putchar()了,这样才能正确地读取并输出文件。如果重载&&运算符,那么先需要计算getchar()和putchar()的结果,再执行&&运算符的重载定义,这样可能会导致一些不可知的错误。这样的重载,会导致编译器在处理&&和||运算符时产生混乱,所以是比较危险的。
    对于逗号表达式来说,默认情况下,编译器按照逗号表达式规定的顺序计算各个表达式。但是如果重载操作逗号表达式,因为需要先检查逗号两边的表达式类型,来判断是否使用重载定义的类型,所以会导致计算顺序的混乱。这样比较危险,会产生一些不可知的错误。虽然在C++并没有限制这3个运算符的重载问题,但是从这个例程和MISRA C++的规则来看,有些时候会产生一些不可预知的错误,所以MISRA C++不允许重载上面3个运算符。[!--empirenews.page--]
    规则5-3-3(强制):单目运算符&不允许被重载。

    
    f1.cc和f2.cc的区别就在于f1.cc只声明了A类,而f2.cc包含了A.h。f1.cc仅声明A类,不会使用A类定义的重载运算,所以f1.cc的8L运算符使用C++内部的取地址定义。f2.cc包含了头文件A.h,因为A.h包含了A类的完整定义,所以f2.cc的&运算符就会使用用户定义的重载操作。在同样一个工程中,仅仅是对A类的声明不同,就导致了在f2.cc中,&a使用用户定义的&运算符含义,而在f1.cc中,&a使用C++内部定义的&运算符含义。
    这样差别会导致程序员在重载&运算符后,无法得知&运算符有没有使用重载的定义。这样做是比较危险的,可能会产生与程序员意愿不同的结果。虽然在C++中并没有限制对单目运算符的重载操作,但是从上面的例程可以看出,MISRA C++不允许重载&运算符是很有必要的。


2 虚函数的使用
    虚函数是C++中一类特殊的函数。在基类中定义一个虚函数,就说明该函数在派生类中可能有不同的实现方式。当派生类的实例调用这个虚函数时,首先会在派生类中去查看该函数有没有被定义。如果派生类定义了这个函数,则执行派生类的函数;否则,在派生路径上寻找最近的该函数的定义,并调用该函数。
    如果从基类派生出多个派生类,那么每个派生类都可以重新定义这个虚函数。如果通过基类的指针指向派生类的对象,并访问该虚函数,会对应地调用每个派生类的函数定义。这样通过基类类型的指针,就可以使属于不同派生类的对象产生不同的行为,从而实现了运行过程的多态。[!--empirenews.page--]
    关于虚函数,MISRA C++有以下几条规则:
    规则10-3-1(强制):在每一个继承路径上,虚函数只能有一个定义。防止按优先度调用。
    例外:析构函数可以定义为虚函数,在每一个派生类上都可以有定义。
    如果一个函数在同一个类中被声明为纯虚函数,但是还有定义,这样的定义就会被忽略。

    


    

    
    在例程的后半段是关于按优先度调用的解释,表1显示的是例程中每个函数的调用和定义关系。b2.f1()是按照正常的继承关系来调用foo()函数,并且调用的是V类中foo()的定义。d.f2()和d.f1()都是按照优先度调用的。它们虽然最后都是调用了foo()函数,但是经过的继承路径却不相同,而且它们最后只能调用到B1类中foo()的定义。为了防止这种情况发生,所以Misra C++规定,虚函数在一个继承路径上,只能有一个函数定义。



    例程的前半部分描述了多个类的继承关系,每个类都包括对几个函数的定义和声明。这里简单介绍一下f1()函数,读者可以通过表2的内容来理解其他函数。f1在A类中是虚函数,而且有定义,在C类中有定义,所以当D类继承C类时,D类中就不能再有定义(“√”表示可以定义,“*”表示不推荐再继续定义)。例外是f4,虽然它在A类中有定义,但是因为它是纯虚函数,所以它的定义会被忽略。



    这个规则说明,如果在一个继承路径上有两个函数定义,在调用函数时,有可能按照继承的优先度调用函数。这样就会导致函数调用的混乱,可能会调不到程序员希望的函数。这是在实现多态时需要特别注意的地方。关于继承路径上的函数定义,C++并没有明确限制。[!--empirenews.page--]
   从上面的例程可以看出,如果没有这样的限制,就会产生一些混乱,虽然程序能够正常运行,但是不一定能够按照程序员所设计的方式运行。这样的运行方式会出现很多漏洞,所以MISRA C++强制规定在每一个继承路径上,虚函数只能有一个定义。
    规则10-3-2(强制):每一个重载的虚函数应该用关键字virtual来声明。

    
    这样做不需要检查基类,就可以确定函数是否为虚函数。MISRA C++推出这样的规则是为了使C++程序更加完善。
    规则10-3-3(强制):只有被声明为纯虚函数的虚函数,才能被纯虚函数重载。

    
    foo函数在A类中定义为纯虚函数,在B类中被重载为普通虚函数。而C类使用纯虚函数重载foo函数。这样做是不行的。
    B类中foo函数重载A类的foo函数时,是用有定义的虚函数重载纯虚函数,这样做是可以的。
    C类中的foo函数重载B类的foo函数时,是用纯虚函数重载一个非纯虚函数,这样是不行的。在C类中,foo被定义为纯虚函数,在C类的对象调用foo函数时无法调用到B类中的定义。这样的重载导致B类中对foo函数的定义丢失。
    所以MISRA C++不允许使用纯虚函数重载非纯虚函数,这样做的目的也是为了使C++程序更加安全。


3 小 结
    正确并完备地实现C++的多态性,能够充分发挥C++的优势,并且提高程序的可读性和可维护性。如果使用不当,会导致一些想象不到的程序漏洞。MISRA C++针对使用多态性可能产生的一些漏洞,提出了规避的方法与建议。本文列出了其中几条比较关键和实用的规则。关于多态性的其他规则,读者可以查看。MISRA C++(2008),以避免不正确使用多态性所导致的一些程序漏洞。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

Java是一门面向对象的编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向...

关键字: Java C++

星标/置顶 公众号,硬核文章第一时间送达!链接| https://zhuanlan.zhihu.com/p/274473971题很多,先上题后上答案,便于大家思考问题点:1、C和C的特点与区别?2、C的多态3、虚函数实现...

关键字: 腾讯 函数 进程 AI

程序接口是操作系统为用户提供的两类接口之一,编程人员在程序中通过程序接口来请求操作系统提供服务。面向过程语言最基本的单元是过程和函数。

关键字: 程序接口 过程 函数

星标「嵌入式大杂烩」,一起进步!链接:https://www.cnblogs.com/jozochen/p/8541714.html一、问题复现稳定复现问题才能正确的对问题进行定位、解决以及验证。一般来说,越容易复现的问...

关键字: 嵌入式开发 函数 代码 寄存器

基本上,没有人会将大段的C语言代码全部塞入main()函数。更好的做法是按照复用率高、耦合性低的原则,尽可能的将代码拆分不同的功能模块,并封装成函数。C语言代码的组合千变万化,因此函数的功能可能会比较复杂,不同的输入,常...

关键字: 函数 PEN C语言代码 C语言程序

Part1一、让自己习惯C条款01:视C为一个语言联邦C并不是一个带有一组守则的一体语言:他是从四个次语言(C、Object-OrientedC、Template、STL) 组成的联邦政府,每个次语言都有自己的规约。记住...

关键字: TI 函数 ASPECT 编译器

为什么会写篇栈变化的文章?做系统分析的话你肯定遇到过一些crash,oops等棘手问题,一般大家都会用gdb,objdump或者addr2line等工具分析pc位置来定位出错的地方。但是这些分析工具背后的本质原理就不见得...

关键字: 函数 ARM C语言 AI

前言:一转眼从事前端已经6年了,从当时的小白到如今大厂的技术专家,中间也走过不少弯路,从今天开始我会持续更新前端技术文章,并且整体的文章会进行体系梳理,整个知识体系分为:基础精讲,框架讲解,框架及工具原理,前端面试题精讲...

关键字: 函数 GE FUNCTION APP

关于C的lambda是函数还是对象,这其实不是一个一概而论的问题。先说结论:对于有捕获的lambda,其等价于对象。对于没有任何捕获的lambda,其等价于函数!首先,很多C程序员从lambda用法上反推容易发现是对象,...

关键字: 函数

1.说明在工作过程中,我发现在实际使用RTOS完成项目时,理解这些知识仅能达到会用RTOS的水平,要想用好RTOS,还需要了解一些比较细节的机制,否则容易掉坑进去,花大量时间定位问题。本文结合TencentOS-Tiny...

关键字: RTOS 函数

充电吧

89208 篇文章

关注

发布文章

编辑精选

技术子站

关闭