微信号:cpp_coder

介绍:最专业的C/C++编程学习和程序员发展的综合平台.提供C/C++、汇编语言等、STL、MFC、QT等等学习、数据结构算法思维分析、各领域项目经验分享、资源下载、招聘和资讯的综合服务.

被我们忽略的一种类重载的实现方式,很巧妙的实现方式

2015-06-07 10:58 CPP技术网

之前写过一篇文章《C++重载:横向重载与纵向重载》 ,描述的是两种方式的重载。说是横向重载和纵向重载,实则是在重载原理方向的的理解。而在运动状态中,我们也把横向重载称为静态重载,而把纵向重载称为动态重载。因为横向重载需要事先写好重载的函数,编译时确定。而纵向重载,依靠虚函数机制,通过虚表,在运行时基类指针查询虚表得到派生类重载的函数。
以上一段话就是对重载的文章的简要总结。今天要讨论的是在这个基础之上的论题。如果重载基础不过关,先看C++重载这篇文章。
在此,我得首先感谢一位朋友,杨建(380200622)。不知道贴出QQ是否合适,如果不合适,请跟我说一声。因为他在看到我的C++重载的文章后,发现文章少了一种实现方式。我顿时来了兴趣,我也很好奇,难道是我看漏还是新标准的语法呢?通过聊了一会,让我意识到,我确实漏掉了这些知识,一番讨论之后,让我大受裨益,很是感谢。在我的技术知识库中,又添加了一笔财富。说实在的,我非常乐意讨论这些设计实现,可以让我领略到精彩思维的火花。在我自己的写文章论述的过程中,还会迸发更多的火花,非常享受在论述时大脑飞转的感觉。毕竟,人最重要的就是思维(思想了),不是吗?
前面的这篇重载的文章,实际上是讲的重载的原理。而现在要讨论的这个问题,实际上并不是第三种重载,而是实现上的技巧。
在重载的原理中,只有这两种,没有第三种。我们要讨论的是一种比较不常见的一种实现方式。这种方式在有些时候非常有用。下面我先说说它使用的场合,有些朋友认为这个实现没什么用。哲学里有一句话叫做“存在的就是合理的”。我相信这一句话,所以,我仔细想了一下这个实现方式,试图去找出实现的目的和实现的原理基础。经过各种猜想和验证,本文便是对我思考后的一个总结,分享给大家。
先说说应用场景,再介绍这种方式。结合实际的项目开发,我们来看看。假如我们从网上得到一个类,解决某个技术的专用类。因为是个人所写,也不是商业类,所以只是自己用用,然后感觉挺好,就将之打包成dll,分发到网上供大家使用。这种情况太常见了。我以前也写过一个基于ADO封装的数据库操作库,也打包成dll,分享到网上了。为了大家方便使用,我已经将源码都发表在C++技术网的数据库板块中了。第一个篇文章是《ADO数据库编程:公共变量、结构体和函数声明》,其他的就可以顺着这篇文章找到。
这里我就要说明一种情况。因为一般个人发布的这些类,也不会考虑到二次开发之类的,也为了简单,也不会使用复杂的继承,也没打算让别人从发布的类中派生使用等。这也就有一个问题。如果提供的类中,一个方法是无参的,并没有提供有参数的。但是这个无参数的函数非常常用,经常会用到。但是你要用一个有参数的,可以实现你自己的方式。因为没有源码,你无法更改提供的类。你从提供的类中继承,你既想直接使用提供的类的无参函数,又要自己实现一个有参的版本。此时问题就来了。
因为原作者没有考虑到其他人继承的问题,就没有给函数提供虚函数的特性。如果你在派生类中想直接使用基类的无参版本,而你的派生类中只提供一个有参版本,此时要使用的只能是有参的版本。因为此时,派生类的函数名与基类的函数名同名,直接使用派生类对象调用,则只能使用的是派生类中有的函数。此时就只能使用派生类的有参数版本。直接使用无参版本,编译不通过。因为派生来中根本没有无参的版本。当然,我们可以使用基类的作用域来使用基类的函数。
下面是代码示例,基类就直接给出,可以说明这个道理。

#include <iostream>
using namespace std;
class Base
{
public:
   void show(){cout<<"我是基类Base.\n";}
};

class A:public Base
{
public:
    void show(int i){cout<<i<<" : 我是A中覆盖了基类的show函数\n";}
};


void main()
{
    A a;
    a.show(2);
    //a.show();// - 编译错误
    //Base::show();// - Base非静态版本,不能直接使用
    A* pA = &a;
    pA->Base::show();// - 使用指针和基类的作用域来使用基类的函数
    a.Base::show();// - 直接使用对象和基类的作用域来使用基类的函数
    ((Base)a).show();// - 直接将a对象转成基类对象,也可以。
}


以上代码可以清楚的说明这个问题。A中只有一个带参数的show函数,基类只有无参的show函数。此时在main中使用A来调用show,只能调用带参数的版本。此时A的实现,也正好覆盖了基类Base类的show,因此,A能看到的只有一个带参数的show。所以调用无参的show编译会错误,因为A类不存在。而如果直接使用Base来访问,因为show在Base中不是静态函数,所以不能直接用作用域直接调用,因为它不存在类中,只存在于实例化的对象中。而后面通过A对象来访问,说明一个问题,基类的show还是存在的,与A类中的show同时存在。这里我想提醒一下,很多人认为派生类覆盖了基类的函数,以为就是派生类的函数替换了基类的函数。其实不是的。只是因为在A类中,因为函数名具有唯一性,函数同名后,就只确定为A类中的了,也可以说,派生类中的函数优先级高一些。但是因为基类的函数确实还是在派生类中,只是A类直接看不到,被A类自己的函数挡住了视线罢了。所以可以通过A类对象,再通过基类的作用域找到基类的这个同名函数show。看上去,这个基类的show函数使用的语法有点奇葩,看多了就习惯了。哈哈哈。当然,好理解的方式就是最后一个,把a转换成基类对象就可以了。关于基类派生类转换的深入理解,请阅读《C++继承:继承模型与内存模型的关系分析》 。
从以上代码可以看出,确实可以在覆盖了基类的函数的情况下,可以使用两个函数,但是,在使用基类的函数是就显得特别麻烦了。甚至使用的代码还可能出现奇葩的写法,让人看着难受。那么,有没有一个好的方法可以解决这个问题呢?那么,到此,就是我们今天说的这个函数重载的实现方式了。
我们要实现的就是在派生类中,将基类的函数版本作为派生类的重载版本,这样,直接使用派生类对象就可以直接使用,和本来就直接在派生类中实现函数的重载(横向重载)是一样的感觉。实现的方法就是,通过using ,将基类的函数名符号引入到派生类中,进而将基类的这些函数当做了派生类的函数的一个或者多个重载版本。如果基类有重载版本,那么这样通过using,就可以将基类的所有的重载版本都导入到了派生类。而派生了再写额外的版本,就与基类导入的函数在同一个命名空间下,这样就符合了函数重载的要求,这样就实现了函数的横向重载。在使用这些函数时,不管是基类的还是派生类的,都可以使用对象来访问,而不需要使用基类的作用域来访问了。这样使用基类函数就非常方便了。同时,这个也是对于基类没有提供重载特性(虚函数)的一种重载补充方式。这样就扩展了基类。不是派生类不想写一个与基类一样的版本覆盖掉基类的,而是,有时候你无法知道基类的这个版本怎么实现的,后者你不想再写一遍,挺麻烦的。使用这种方式,只要把基类的函数导入到自己的类的函数列表中,就可以快捷的使用基类的函数,这不是两全其美的事么。可以不写代码,就可以达到快捷使用基类函数。引入的函数,只是引用一个函数名。这种引入的方式,实际上相当于我们常见的快捷方式而已。函数的实现代码还是在基类。只是这种实现方式让我们使用起来方便了,本质上,并没有多出一个函数。
论起这个重载的实现方式的本质,还是属于横向重载。重载的地方就很巧妙,准确来说,是在派生类中实现的重载,基类的函数只是挂个名凑个热闹罢了。这个技术是通过命名空间机制和作用域机制以及横向重载机制综合起来实现的。说到底,它只是一个巧妙的实现罢了,还是横向重载。而说起效率,与虚函数重载,那就是无从谈起。因为这个表面上是类似于纵向重载,通过分析发现,它是横向重载。因此,和纵向的虚函数重载不是一个东西,没法比较。也正是因为这种实现方式又有纵向的(命名空间引用函数符号),也有横向的(派生类的函数重载),所以,可能大家开始了解的时候很容易迷糊。还要提到一点,因为是在派生类实现的横向重载,所以,基类没有任何变动,所以,也不要希望通过另外写一个类继承于基类Base,然后就可以访问在派生类A中的重载版本。通过本文的分析,我想,大家应该会很清楚了吧。
这种重载方式的实现代码如下:

#include <iostream>
using namespace std;
class Base
{
public:
   void show(){cout<<"我是基类Base.\n";}
};

class A:public Base
{
public:
    using Base::show;
    void show(int i){cout<<i<<" : 我是A中覆盖了基类的show函数\n";}
};
void main()
{
    A a;
    a.show(2);
    a.show();
}


通过这种方式的重载后,你可以清楚的发现,再使用基类的函数,就和A类中的其他重载版本一样,代码看起来很简单自然了,也没有对无参版本重新写一遍。同时你也无需知道基类的无参版本是如何实现的。

最佳阅读效果,请阅读原文。

 
CPP技术网 更多文章 宏、常量、枚举、结构体和共用体对比分析之常量 宏、常量、枚举、结构体和共用体对比分析之结构体 C/C++声明定义初始化和赋值独家剖析深刻理解 mfc的消息机制,多窗口的互动 MFC中的窗口类:C++类与窗口句柄的结合深入浅出分析
猜您喜欢 win10功能大盘点,畅玩win10! 如何通过第三方工具提高电商数据转化 Python的四个挑战者:Swift、Go、Julia、R “高富帅”研究报告:教育&租房篇 Quora是如何做推荐的?