C++中的Overload、Override和Overwrite


C++中的Overload、Override和Overwrite

VictoKu 2014-11-18 原文

  在C++语言中有一组基础的概念一直都容易混淆:Overload、Override和Overwrite分别表示什么意思?下面把这三个概念整理一下:

1. Overload(重载)

  重载的概念最好理解,在同一个类声明范围中,定义了多个名称完全相同、参数(类型或者个数)不相同的函数,就称之为Overload(重载)。重载的特征如下:

(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。

2. Override(覆盖)

  覆盖的概念其实是用来实现C++多态性的,即子类重新改写父类声明为virtual的函数。Override(覆盖)的特征如下:

(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数列表完全相同;
(4)基类函数必须有virtual 关键字。

3. Overwrite(改写)

  改写是指派生类的函数屏蔽(或者称之为“隐藏”)了与其同名的基类函数。正是这个C++的隐藏规则使得问题的复杂性陡然增加,这里面分为两种情况讨论:

(1)如果派生类的函数与基类的函数同名,但是参数不同。那么此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。那么此时,基类的函数被隐藏(注意别与覆盖混淆)。

  借鉴一个网上的例子来看Overwrite(改写)的情况:

   
  1. #include
  2. using namespace std;
  3.  
  4. class Base
  5. {
  6. public:
  7. virtual void f(float x){ cout << "Base::f(float) " << x << endl; }
  8. virtual void g(float x){ cout << "Base::g(float) " << x << endl; }
  9. void h(float x){ cout << "Base::h(float) " << x << endl; }
  10. };
  11.  
  12. class Derived : public Base
  13. {
  14. public:
  15. virtual void f(float x){ cout << "Derived::f(float) " << x << endl; }
  16. virtual void g(int x){ cout << "Derived::g(int) " << x << endl; }
  17. void h(float x){ cout << "Derived::h(float) " << x << endl; }
  18. };
  19.  
  20. int main()
  21. {
  22. Derived d;
  23. Base *pb = &d;
  24. Derived *pd = &d;
  25.  
  26. // Good : behavior depends solely on type of the object
  27. pb->f(3.14f); // Derived::f(float) 3.14
  28. pd->f(3.14f); // Derived::f(float) 3.14
  29.  
  30. // Bad : behavior depends on type of the pointer
  31. pb->g(3.14f); // Base::g(float) 3.14 (surprise!)
  32. pd->g(3.14f); // Derived::g(int) 3
  33.  
  34. // Bad : behavior depends on type of the pointer
  35. pb->h(3.14f); // Base::h(float) 3.14 (surprise!)
  36. pd->h(3.14f); // Derived::h(float) 3.14
  37.  
  38. return ;
  39. }
   

  在上面这个例子中:

  • 函数Derived::f(float)覆盖(override)了Base::f(float)。
  • 函数Derived::g(int)改写/隐藏(overwrite)了Base::g(float)。
  • 函数Derived::h(float)改写/隐藏(overwrite)了Base::h(float)。

4. 特殊情况说明

  除了上面讲到的三种情况之外,还有一些比较容易迷惑的地方,例如:

4.1 同名的普通函数与const函数本质上是两个不同的函数,应该等价理解为这两个同名函数的参数是不同的。在派生类中的virtual函数理解上可能会有误解。

  参见如下例子:

  1. #include
  2. using namespace std;
  3.  
  4. class Base
  5. {
  6. public:
  7. virtual void f(float x){ cout << "Base::f(float) " << x << endl; }
  8. };
  9.  
  10. class Derived : public Base
  11. {
  12. public:
  13. virtual void f(float x) const { cout << "Derived::f(float) " << x << endl; }
  14. };
  15.  
  16. int main()
  17. {
  18. Derived d;
  19. Base *pb = &d;
  20. Derived *pd = &d;
  21.  
  22. // Bad : behavior depends solely on type of the object
  23. pb->f(3.14f); // Base::f(float) 3.14
  24. pd->f(3.14f); // Derived::f(float) 3.14
  25.  
  26. return ;
  27. }

4.2 基类中定义的virtual虚函数,在继承子类中同名函数自动都属于虚函数,可以不需要virtual关键字。

4.3 如果基类中定义的函数不是virtual,而子类中又将相同函数定义为virtual,则称之为越位,函数行为依赖于指针/引用的类型,而不是实际对象的类型。

  参见如下例子:

  1. #include
  2. using namespace std;
  3.  
  4. class Base
  5. {
  6. public:
  7. void f(){ cout << "Base::f() " << endl; }
  8. virtual void g(){ cout << "Base::g() " << endl; }
  9. };
  10.  
  11. class Derived : public Base
  12. {
  13. public:
  14. virtual void f(){ cout << "Derived::f() " << endl; }
  15. void g(){ cout << "Derived::g() " << endl; }
  16. };
  17.  
  18. class VirtualDerived : virtual public Base
  19. {
  20. public:
  21. void f(){ cout << "VirtualDerived::f() " << endl; }
  22. void g(){ cout << "VirtualDerived::g() " << endl; }
  23. };
  24.  
  25. int main()
  26. {
  27. Base *d = new Derived;
  28. Base *vd = new VirtualDerived;
  29.  
  30. d->f(); // Base::f() Bad behavior
  31. d->g(); // Derived::g()
  32.  
  33. vd->f(); // Base::f() Bad behavior
  34. vd->g(); // VirtualDerived::g()
  35.  
  36. delete d;
  37. delete vd;
  38.  
  39. return ;
  40. }

 5. 针对非虚函数的继承说明

  在《Effective C++》中讲述了这样一个规则:任何条件下都要禁止重新定义继承而来的非虚函数。

  公有继承的含义是 "是一个"(is a),"在一个类中声明一个非虚函数实际上为这个类建立了一种特殊性上的不变性"。如果将这些分析套用到类B、类D和非虚成员函数B::mf,那么:

  (1)适用于B对象的一切也适用于D对象,因为每个D的对象“是一个”B的对象。
  (2)B的子类必须同时继承mf的接口和实现,因为mf在B中是非虚函数。

  那么,如果D重新定义了mf,设计中就会产生矛盾。如果D真的需要实现和B不同的mf,而且每个B的对象(无论怎么特殊)也真的要使用B实现的mf,那么每个D将不 "是一个" B。这种情况下,D不能从B公有继承。相反,如果D真的必须从B公有继承,而且D真的需要和B不同的mf的实现,那么,mf就没有为B反映出特殊性上的不变性。这种情况下,mf应该是虚函数。最后,如果每个D真的 "是一个" B,并且如果mf真的为B建立了特殊性上的不变性,那么,D实际上就不需要重新定义mf,也就决不能这样做。

  不管采用上面的哪一种论据都可以得出这样的结论:任何条件下都要禁止重新定义继承而来的非虚函数。