覆盖虚函数和隐藏非虚函数有什么区别
What are the differences between overriding virtual functions and hiding non-virtual functions?
给定以下代码片段,函数调用有什么区别?什么是函数隐藏?什么是函数覆盖?它们与函数重载有何关系?两者有什么区别?我无法在一个地方找到对这些的良好描述,所以我在这里询问,以便我可以整合信息。
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();
}
什么是函数隐藏?
。是名称隐藏的一种形式。一个简单的例子:
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
}
};
<小时 />什么是函数覆盖?
这与虚拟功能的概念有关。[类.虚拟]/2
如果一个虚成员函数
vf
是在类Base
和类Derived
中声明的,直接或间接派生自Base
,则成员函数vf
具有与声明Base::vf
相同的名称、参数类型列表、cv-限定符和ref限定符(或不存在相同),则Derived::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)`
};
对于函数重写,只有基类中具有相同参数的函数才会被覆盖;当然,您可以重载虚函数:
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)`
};
调用虚拟成员函数和调用非虚拟成员函数之间的区别在于,根据定义,在前一种情况下,目标函数是根据调用中使用的对象表达式的动态类型选择的,而在后一种情况下,使用静态类型。
仅此而已。您的示例通过p2->doA()
和p2->doB()
调用清楚地说明了这种差异。*p2
表达式的静态类型为Parent
,而同一表达式的动态类型为Child
。这就是为什么p2->doA()
打电话Parent::doA
,p2->doB()
打电话Child::doB
。
在这种差异很重要的情况下,名称隐藏根本不会出现。
我们将从简单的开始。
p1
是一个Parent
指针,所以它将始终调用Parent
的成员函数。
cp
是一个指向Child
的指针,所以它将永远调用Child
的成员函数。
现在更难的那个。 p2
是一个Parent
指针,但它指向一个类型为 Child
的对象,所以只要匹配的函数Parent
是虚拟的,或者函数只存在于Child
中而不存在于Parent
中,它就会调用Child
的函数。换句话说,Child
用自己的doA()
隐藏Parent::doA()
,但它覆盖了Parent::doB()
。函数隐藏有时被认为是函数重载的一种形式,因为具有相同名称的函数被赋予了不同的实现。由于隐藏函数与隐藏函数位于不同的类中,因此它确实具有不同的签名,这清楚地表明要使用哪个签名。
testStuff()
的输出将是
doA in Parent
doA in Parent
doA in Child
doB in Parent
doB in Child
doB in Child
在任何情况下,都可以使用名称解析在Child
内调用Parent::doA()
和Parent::doB()
,而不管函数的"虚拟性"如何。函数
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
可以强制转换为Child*
,因为它被初始化为一个,然后可以用来调用doX()
。
一个更简单的例子,与所有这些黑白不同。
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
}
您在问题中编写的示例代码在运行时基本上给出了答案。
调用非虚函数将使用与指针类型相同的类中的函数,而不管该对象是否实际上是作为其他派生类型创建的。而调用虚拟函数将使用原始分配对象类型的函数,而不管您使用哪种指针。
因此,在这种情况下,程序的输出将是:
doA in Parent
doA in Parent
doA in Child
doB in Parent
doB in Child
doB in Child
- 什么时候调用组成单元对象的析构函数
- 当在同一名称空间中有两个具有相同签名的函数时,会发生什么
- 有没有什么方法可以使用一个函数中定义的常量变量,也可以由c++中同一程序中的其他函数使用
- 什么时候调用析构函数
- 在两台机器之间进行时间戳的最佳c++chrono函数是什么
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 是什么原因导致它无法编译?它是声明签名还是在函数本身的实现中
- 我可以在这里替换什么,因为我不能在 C# 中使用隐式变量的 lambda 函数?
- 是什么让放置新调用对象的构造函数?
- 重载运算符的范围是什么?它是否会影响作为类成员的集合的插入函数?
- 如果我真的真的想从 STL 容器继承,并且我继承构造函数并删除新运算符,会发生什么?
- 函数名称表示什么等等
- 在函数中拥有函数原型的目的是什么?
- 使用基类指针调用基类的值构造函数的语法是什么?
- 什么是自定义比较器以及如何在 C++ 的排序函数中使用它?
- C++:使用方法调用析构函数的顺序是什么?
- 将此布尔值传递给此函数的最有效方法是什么?
- lambda函数什么时候对C++中的类有用
- 函数什么时候必须在c++中返回引用
- 如果void类型函数什么都不返回会发生什么