一、看一个隐藏non-virtual函数的例子
- 假设class D以public的方式继承于class B,代码如下:
class B {
public:
void mf();
};
class D :public B {};
int main()
{
D x;
B *pB = &x;
pB->mf(); //调用B::mf()
D *pD = &x;
pD->mf(); //调用D::mf()
return 0;
}
二、静态绑定与动态绑定
- 关于静态绑定、动态绑定的概念之前,大家先了解下静态类型的类变量和动态类型的类变量概念和区别。
- 静态类型的类变量:在编译时就已经知道是什么类型的了
- 动态类型的类变量:自己所指的类型不明确,直到运行时才知道
- 如果表达式既不是引用也不是指针,那么其就没有静态类型和动态类型的概念,因为其只能与自己类型一致的对象绑定到一起
演示案例
- 当我们使用基类的引用(或指针)时,我们并不清楚该引用(或指针)所绑定的对象的真实类型,该对象可能是基类的对象,也可能是派生类的对象。只有在程序运行的时候我们才知道所绑定的对象的真实类型
- class A {};
- class B:public A{};
- int main()
- {
- A a; //静态类型
- B b; //静态类型
- A *p; //动态类型
- p = &a; //p指向了a
- p = &b; //p又指向了b
- return 0;
- }
- class A {
- protected:
- int a;
- public:
- void setA(int num) { a = num; }
- void cout_getA() { cout << "A" << a << endl; }
- };
- class B :public A {
- public:
- void setA(int num) { a = num; }
- void cout_getA() { cout << "B" << a << endl; }
- };
- int main()
- {
- A a;
- B b;
- a.setA(10);
- b.setA(20);
- A *p1, *p2;
- p1 = &a;
- p2 = &b;
- p1->cout_getA();
- p2->cout_getA();
- return 0;
- }
- 结果解析:
- A 10:这个比较简单,因为a的类型为A,且指针也为A,因此调用A的getA()函数
- A 20:虽然p2指针指向的类类型为B,但是访问规则只与指针/引用的类类型有关,而与指针/引用指向的类型无关。因此b已经被视为一个A对象来看了。此处p2指针的类型为A,因此调用A的getA()函数。又因为b对象使用setA()函数将整个继承体系中的a改为了20,因此打印出来的a为20
静态绑定
- 当我们调用non-virtual函数时,调用的函数版本与指针的类型有关
- 例如,上面的pB指针在初始化时,将与D对象中的B对象所绑定;上面的pD指针在初始化时,将与D对象所绑定。这是静态绑定
- 因此,pB调用的是B::mf();pD调用的是D::mf()
动态绑定
- 当我们调用virtual函数时,调用的函数版本与指针所指的对象有关
- 对virtual函数的调用,是在代码运行期间执行的。例如,如果上面的D::mf()是一个虚函数,那么会有:
class B {
public:
virtual void mf();
};
class D :public B {
public:
virtual void mf();
};
int main()
{
D x;
B *pB = &x;
pB->mf(); //调用D::mf()
D *pD = &x;
pD->mf(); //调用D::mf()
return 0;
}
三、为什么不建议派生类隐藏基类的non-virtual函数
- 在条款32介绍过,public继承意味着is-a关系,条款34描述了为什么在class内声明一个non-virtual函数会为该class建立起一个不变性,凌驾其特异性。如果:
- 我们在派生类中隐藏了基类的non-virtual函数,那么基类与派生类就会产生行为上的不一致,is-a关系就消失了
- 如果想要表现出派生类与基类的不同,那么应该将函数声明为virtual(其中虚析构函数是一个例子)
四、总结
- 绝对不要重新定义继承而来的non-virtual函数