覆盖虚拟功能和隐藏非虚拟功能有什么区别?

时间:2013-11-01 22:15:58

标签: c++ inheritance virtual-functions override method-hiding

鉴于以下代码片段,函数调用有何不同?什么是隐藏功能?什么是功能重写?它们如何与函数重载相关?两者有什么区别?我在一个地方找不到这些的好描述,所以我在这里问我所以我可以巩固这些信息。

class Parent {
  public:
    void doA() { cout << "doA in Parent" << endl; }
    virtual void doB() { cout << "doB in Parent" << endl; }
};

class Child : public Parent {
  public:
    void doA() { cout << "doA in Child" << endl; }
    void doB() { cout << "doB in Child" << endl; }
};

Parent* p1 = new Parent();
Parent* p2 = new Child();
Child* cp = new Child();

void testStuff() {
  p1->doA();
  p2->doA();
  cp->doA();

  p1->doB();
  p2->doB();
  cp->doB();
}

5 个答案:

答案 0 :(得分:23)

什么是隐藏功能?

...是隐藏名称的一种形式。一个简单的例子:

void foo(int);
namespace X
{
    void foo();

    void bar()
    {
        foo(42); // will not find `::foo`
        // because `X::foo` hides it
    }
}

这也适用于基类中的名称查找:

class Base
{
public:
    void foo(int);
};

class Derived : public Base
{
public:
    void foo();
    void bar()
    {
        foo(42); // will not find `Base::foo`
        // because `Derived::foo` hides it
    }
};

什么是功能覆盖?

这与虚拟功能的概念有关。 [class.virtual] / 2

  

如果在类vf和类Base中声明虚拟成员函数Derived,直接或间接地从Base派生,则成员函数{{1声明同名,参数类型列表,cv-qualification和ref-qualifier(或缺少相同),vf也是虚拟的(无论是否为如此声明)并且它覆盖 Base::vf

Derived::vf

调用虚函数时,最后的覆盖变得相关:[class.virtual] / 2

  

类对象Base::vf的虚拟成员函数class Base { private: virtual void vf(int) const &&; virtual void vf2(int); virtual Base* vf3(int); }; class Derived : public Base { public: // accessibility doesn't matter! void vf(int) const &&; // overrides `Base::vf(int) const &&` void vf2(/*int*/); // does NOT override `Base::vf2` Derived* vf3(int); // DOES override `Base::vf3` (covariant return type) }; 是最终覆盖,除非C::vf是基类子对象(如果有)的最派生类声明或继承另一个覆盖S

的成员函数

即。如果你有一个S类型的对象,那么最后的覆盖是你在vf的类层次结构遍历到它的基类时看到的第一个覆盖。重要的一点是函数调用表达式的动态类型用于确定最终的覆盖:

S

覆盖和隐藏有什么区别?

基本上,基类中的函数总是被派生类中的同名函数隐藏;无论派生类中的函数是否覆盖基类的虚函数:

S

要查找函数名称,请使用表达式的静态类型:

Base* p = new Derived;
p -> vf();    // dynamic type of `*p` is `Derived`

Base& b = *p;
b  . vf();    // dynamic type of `b` is `Derived`

它们如何与函数重载相关?

由于“函数隐藏”是名称隐藏的一种形式,如果隐藏了函数的名称,所有重载都会受到影响:

class Base
{
private:
    virtual void vf(int);
    virtual void vf2(int);
};

class Derived : public Base
{
public:
    void vf();     // doesn't override, but hides `Base::vf(int)`
    void vf2(int); // overrides and hides `Base::vf2(int)`
};

对于函数重写,只有具有相同参数的基类中的函数才会被覆盖;你当然可以重载一个虚函数:

Derived d;
d.vf(42);   // `vf` is found as `Derived::vf()`, this call is ill-formed
            // (too many arguments)

答案 1 :(得分:5)

调用虚拟成员函数和调用非虚拟成员函数之间的区别在于,根据定义,在前一种情况下,目标函数是根据使用调用中使用的对象表达式的 dynamic 类型,而在后一种情况下使用 static 类型。

这就是它的全部内容。您的示例通过p2->doA()p2->doB()调用清楚地说明了这种差异。 *p2表达式的静态类型为Parent,而同一表达式的动态类型为Child。这就是p2->doA()调用Parent::doAp2->doB()调用Child::doB的原因。

在这种差异很重要的情况下,名字隐藏根本不会出现在图片中。

答案 2 :(得分:2)

我们将从简单的开始。

p1Parent指针,因此它始终会调用Parent的成员函数。

cp是指向Child的指针,因此它始终会调用Child的成员函数。

现在越困难了。 p2Parent指针,但它指向Child类型的对象,因此只要匹配Child函数,它就会调用Parent的函数是虚拟的,或者该函数仅存在于Child内,而不存在于Parent中。换句话说,Child会将Parent::doA()隐藏为doA(),但会覆盖Parent::doB()。函数隐藏有时被认为是函数重载的一种形式,因为具有相同名称的函数被赋予不同的实现。由于隐藏功能与隐藏功能属于不同的类,因此它具有不同的签名,这使得可以清楚地使用哪个。

testStuff()的输出将为

doA in Parent
doA in Parent
doA in Child
doB in Parent
doB in Child
doB in Child

在任何情况下,Parent::doA()Parent::doB()都可以使用名称解析在Child内调用,无论函数的“虚拟”如何。功能

void Child::doX() {
  doA();
  doB();
  Parent::doA();
  Parent::doB();
  cout << "doX in Child" << endl;
}

cp->doX()通过输出

调用时演示此内容
doA in Child
doB in Child
doA in Parent
doB in Parent
doX in Child

此外,cp->Parent::doA()会调用Parent的{​​{1}}版本。

doA()无法引用p2,因为它是doX(),而Parent*不知道Parent中的任何内容。但是,Child可以投放到p2,因为它已初始化为1,然后可以用来调用Child*

答案 3 :(得分:2)

一个更简单的例子,它们与所有这些都有不同。

class Base {
public:
    virtual int fcn();
};

class D1 : public Base {
public:  
    // D1 inherits the definition of Base::fcn()
    int fcn(int);  // parameter list differs from fcn in Base
    virtual void f2(); // new virtual function that does not exist in Base
};

class D2 : public D1 {
public:
    int fcn(int); // nonvirtual function hides D1::fcn(int)
    int fcn();  // overrides virtual fcn from Base
    void f2();  // overrides virtual f2 from D1
}

答案 4 :(得分:1)

您在问题中编写的示例代码在运行时基本上给出了答案。

调用非虚函数将使用与指针类型相同的类中的函数,而不管该对象是否实际创建为其他派生类型。而调用虚函数将使用原始分配对象类型中的函数,而不管您使用的是哪种指针。

因此,在这种情况下,您的程序输出将是:

doA in Parent
doA in Parent
doA in Child
doB in Parent
doB in Child
doB in Child