C++ 避免隐藏继承而来的名称


    关于C++中继承这个概念相比大家都很熟悉,那么子类究竟能从父类继承到哪些东西,哪些东西又是子类继承不到的呢?可能很多人都会觉得父类所有的东西都会被子类继承,包括成员函数和成员变量,否则就违背了“父子关系”这字面上的意思,其实不然,首先我们先看一小段简单的代码,这段代码很容易理解


#include <iostream>

using namespace std;
class Base
{
public:
    virtual void fun1() = 0;
    virtual void fun1(int)
    {

    }
    virtual void fun2()
    {

    }
    void fun2(int)
    {

    }

    void fun3(float)
    {

    }
};

class Derived:public Base
{
public:
    virtual void fun1()
    {

    }
    void fun3()
    {

    }
    void fun4()
    {

    }

};


int main()
{
    Derived a;
    a.fun1(); //correct Derived:fun1();
    //a.fun1(8); //incorrect
    a.fun2(); //correct Base:fun2()
    a.fun2(3); //correct Base:fun2(int);
    a.fun3(); //correct Derived:fun3()
    //a.fun3(3.0); //incorrect
    cout << "Hello world!" << endl;
    return 0;
}

仔细看主函数里面的注释,这段代码几乎会让很多人第一次看到都很诧异


首先普及两点常识,大虾略过

1.  重载这个概念很重要,这是C++比C多出来的功能,我们知道C是不支持函数重载的,这也是限制C开发大型程序的原因之一。

    重载发生在同一个类中(父类和子类不叫同一类,虽然可以称他们为同一类型)

    函数名称相同,参数不同

    函数重载忽略返回值和virtual关键字(如果仅仅是返回值不同或有无virtual关键字的区别,不算函数重载)


下面是对函数重载为什么忽略返回值的解释:

    void Function(void);
    int Function (void);
    上述两个函数,第一个没有返回值,第二个的返回值是int 类型。如果这样调用函数:
    int x = Function ();
   则可以判断出Function 是第二个函数。问题是在C++/C 程序中,我们可以忽略函数的返回值。在这种情况下,编译器和程序员都不知道哪个Function 函数被调用。
   所以只能靠参数而不能靠返回值类型的不同来区分重载函数。编译器根据参数为每个重载函数产生不同的内部标识符



2.  隐藏这个概念
    隐藏就是“内部作用域”的名称隐藏"外部作用域“的名称,例如局部变量会隐藏同名的全局变量,同样, 子类的名称会隐藏父类的名称。这篇文章主要解释的就是关于继承中存在的隐藏的问题

    好了,有了上面的知识,我们就可以对上面的代码加以分析了。Base类中的fun1和fun2函数都被重载了,我们在主函数中调用fun1()函数是没有问题的,因为在Derived类中存在这个函数,这里我们调用的fun1()就是Derived类中的fun1()。再往后面看,我们调用fun1(8)函数会出现错误,在Base类中明明存在virtual void fun1(int)这个函数,我们又使用了public继承,为什么这里调用会出错呢?难道是没有继承到fun1(int)这个函数,是不是很奇怪?这是因为子类中存在fun1()函数,编译器在子类中找到fun1()这个符号,就不会再去父类里查找fun1(int),虽然父类里面有我们需要的函数。但是编译器不管,只要找到fun1()这个符号,它就再往下查找。这就导致了父类中所有与子类同名的函数都将被隐藏,而这肯定与我们绝大多数时候的意愿是相违背的。


    C++这么做是有原因的,记住一句话,C++每一种行为的背后都有其深层的原理,搞清楚这背后的原理对我们理解C++很有帮助。
    关于这种行为背后原理的权威解释,在《Effetive C++》中有这么一段话:
   ”
这些行为背后的原理是为了防止你在程序库或应用框架内建立新的derived class时从疏远的的base classes继承重载函数“。
    仔细分析下这句话C++这么做确实有道理的,如果程序规模比较大,类继承层次比较深时,如果C++没有这种隐藏特性,那么子类中的重载函数数量就会非常多,这样就增加了我们调用这些函数出错的可能性。但是很多情况下我们不需要编译器这样”弄巧成拙“,那么如何解决这个问题,也就是让编译器不要隐藏域子类同名的函数。
显然C++是给出了解决办法的,我们可以使用using声明来解决这个问题。看下面的代码


#include <iostream>

using namespace std;
class Base
{
public:
    virtual void fun1() = 0;
    virtual void fun1(int)
    {

    }
    virtual void fun2()
    {

    }
    void fun2(int)
    {

    }

    void fun3(float)
    {

    }
};

class Derived:public Base
{
public:
    using Base::fun1;
    using Base::fun2;
    using Base::fun3;
    virtual void fun1()
    {

    }
    void fun3()
    {

    }
    void fun4()
    {

    }

};


int main()
{
    Derived a;
    a.fun1(); //correct Derived:fun1();
    a.fun1(8); 
    a.fun2(); //correct Base:fun2()
    a.fun2(3); //correct Base:fun2(int);
    a.fun3(); //correct Derived:fun3()
    a.fun3(3.0); 
    cout << "Hello world!" << endl;
    return 0;
}

 通过在Derived类中引入using申明,我们将Base类中的所有fun1,fun2, fun3函数添加进了子类,这样子类对象在调用这些函数时就没有任何问题了。


    所以当我们继承父类,而且父类存在相同函数,我们又在子类重新定义了这些相同函数的一部分,那么我们必须为那些原本会被隐藏的名称加上using申明,否则那些我们希望继承的名称会被隐藏掉而又不给我们任何提示。


    现在让我们再深入一点,如果我们不希望继承所有父类中的成员函数(这里与今天的主题并不矛盾),这在public继承中是不可能的,也是没有意义的。在public继承中父类和子类关系式is-a。然后这在private继承中是有意义的,这么说吧,假设我们只想继承Base类中带参数的fun2版本,即void fun2(int)这个函数。上面提到的using声明显然已经不足以解决这个问题,因为using声明会将父类中所有指定名称的函数都在子类中可见,所以需要另外的解决办法,看代码:

   


#include <iostream>

using namespace std;
class Base
{
public:
    virtual void fun1() = 0;
    virtual void fun1(int)
    {

    }
    virtual void fun2()
    {

    }
    void fun2(int)
    {

    }

    void fun3(float)
    {

    }
};

class Derived:private Base
{
public:
    virtual void fun1()
     {
            cout<<"This function is implemented to avoid to be a abstract class";
     }
    void CallBaseFun2(int x)
    {
            Base::fun2(x);
    }


};

int main()
{
    Derived a;
    a.CallBaseFun2(3);
    //a.fun2(); //incorrect 

    cout << "Hello world!" << endl;
    return 0;
}


    在这段代码中,将Base类中的fun2(int)函数放在Derived类中的CallBaseFun2(int x)函数中实现调用,而Base中的无参数fun2版本void fun2()不能调用。好了,关于C++中继承和隐藏的类容就差不多了。




总结:
    Derived类中的名称会隐藏Base类中同名的名称,在public继承中我们可以通过引入using声明,在private继承中我们可以通过一个转接函数来解决这个问题。


   
















本文转自:

https://blog.csdn.net/xiaoshengqdlg/article/details/51776946


   




阅读更多
想对作者说点什么? 我来说一句

名称解析

名称解析

学院

2015年08月06日 10:13

没有更多推荐了,返回首页

不良信息举报

C++ 避免隐藏继承而来的名称

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭