30

给定以下代码片段,函数调用有什么区别?什么是函数隐藏?什么是函数覆盖?它们与函数重载有什么关系?两者有什么区别?我在一个地方找不到对这些的很好的描述,所以我在这里问,以便我可以整合信息。

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();
}
4

5 回答 5

36

什么是函数隐藏?

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

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中声明了一个虚成员函数,直接或间接地派生自一个与被声明,然后也是虚拟的(无论它是否被声明)并且它覆盖.BaseDerivedBasevfBase::vfDerived::vf 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)
};

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

C::vf类对象的虚成员函数S是最终覆盖器,除非其最派生类S是基类子对象(如果有)声明或继承了另一个覆盖的成员函数vf

即,如果您有一个类型为 的对象S,则最终覆盖器是您在遍历类层次结构S返回其基类时看到的第一个覆盖器。重要的一点是函数调用表达式的动态类型用于确定最终的覆盖器:

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

Base& b = *p;
b  . vf(42);    // 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)

它们与函数重载有什么关系?

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

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

class Derived : public Base
{
public:
    void vf();     // hides `Base::vf(int)` and `Base::vf(double)`
};

For function overriding, only the function in the base class with the same arguments will be overriden; you can of course overload a virtual function:

class Base
{
private:
    virtual void vf(int);
    virtual void vf(double);
    void vf(char);  // will be hidden by overrides in a derived class
};

class Derived : public Base
{
public:
    void vf(int);    // overrides `Base::vf(int)`
    void vf(double); // overrides `Base::vf(double)`
};
于 2013-11-01T23:49:23.817 回答
7

The difference between calling a virtual member function and calling a non-virtual member function is that, by definition, in the former case the target function is chosen in accordance with the dynamic type of the object expression used in the call, while in the latter case the static type is used.

That's all there is to it. Your example clearly illustrates this difference by p2->doA() and p2->doB() calls. Static type of *p2 expression is Parent, while dynamic type of the same expression is Child. This is why p2->doA() calls Parent::doA and p2->doB() calls Child::doB.

In contexts in which that difference matters, name hiding does not come into the picture at all.

于 2015-06-27T22:21:01.250 回答
3

我们将从简单的开始。

p1是一个Parent指针,所以它总是会调用Parent的成员函数。

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

现在更难的一个。p2是一个Parent指针,但它指向一个类型的对象,所以只要匹配的函数是虚函数或者该函数只存在于而不存在于中Child,它就会调用的函数。换句话说,用它自己的隐藏,但它会覆盖。函数隐藏有时被认为是函数重载的一种形式,因为同名的函数被赋予了不同的实现。因为隐藏函数与隐藏函数在不同的类中,所以它确实有不同的签名,这使得使用哪个更清楚。ChildParentChildParentChildParent::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的版本doA()

p2不能引用,doX()因为它是一个Parent*,并且Parent不知道Child. 但是,p2可以强制转换为 a Child*,因为它被初始化为 1,然后它可以用来调用doX()

于 2013-11-01T22:15:57.507 回答
2

A much easier example that differs b/w all of them.

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
}
于 2015-03-31T18:35:19.043 回答
1

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

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

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

doA in Parent
doA in Parent
doA in Child
doB in Parent
doB in Child
doB in Child
于 2013-11-01T23:30:11.493 回答