C++ Polymorphism 简单探讨

关于 C++ 中多态 Polymorphism 的实现,我们都知道要通过虚函数 virtual function。记得我第一次接触多态的例程的时候,在父类 Base Class 中,并没有在需要子类 Derived Class 重定义的函数前声明 virtual,不过即便如此,也一样实现了多态。后来,我学习到 virtual function 的时候才发现,奇怪,不使用 virtual 也可以在子类中对函数进行重定义,那么 virtual 函数的不同在哪里呢?

讨论 1

C++ 的 non-virtual 函数可以重定义,那么 virtual 函数相比 non-virtual 函数有什么不一样?

结论:虚函数能让你在仅用一个指针进行调用时,确保访问到希望访问的方法,而非虚函数不行。

见如下 Code_1,基类的函数前并没有加 virtual。从输出结果可以看出,pa 指向 Pig 时候,并没有访问到 Pig 的方法。同样下面存放指针的 voa 容器也没有访问到正确的方法。

现在,我们把代码改成如下 Code_2(基类成员函数前加 virtual)。显而易见,现在都访问到了正确的方法。

不过需要注意的是,在这个例子中我并没有写析构函数 dtor,如果(在 Animal 中)写出则必须为 virtual。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <vector>
using namespace std;

class Animal {
public:
void animalSound() { cout << "__Animal_make_a_sound__" << endl; }
};
class Pig : public Animal {
public:
void animalSound() { cout << "__Pig_make_a_sound__" << endl; }
};
class Dog : public Animal {
public:
void animalSound() { cout << "__Dog_make_a_sound__" << endl; }
};

int main(int argc, char **argv) {
Animal _Animal;
Pig _pig;
Dog _dog;
_Animal.animalSound(); //__Animal_make_a_sound__
_pig.animalSound(); //__Pig_make_a_sound__
_dog.animalSound(); //__Dog_make_a_sound__

Pig *pp = new Pig();
Animal *pa = pp;
pa->animalSound(); //__Animal_make_a_sound__
pp->animalSound(); //__Pig_make_a_sound__

vector<Animal *> voa;
voa.push_back(new Pig());
voa.push_back(new Dog());
for (auto x: voa) {
x->animalSound(); //__Animal_make_a_sound__
cout << endl; //__Animal_make_a_sound__
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <vector>
using namespace std;

class Animal {
public:
virtual void animalSound() { cout << "__Animal_make_a_sound__" << endl; }
};
class Pig : public Animal {
public:
void animalSound() { cout << "__Pig_make_a_sound__" << endl; }
};
class Dog : public Animal {
public:
void animalSound() { cout << "__Dog_make_a_sound__" << endl; }
};

int main(int argc, char **argv) {
Animal _Animal;
Pig _pig;
Dog _dog;
_Animal.animalSound(); //__Animal_make_a_sound__
_pig.animalSound(); //__Pig_make_a_sound__
_dog.animalSound(); //__Dog_make_a_sound__

Pig *pp = new Pig();
Animal *pa = pp;
pa->animalSound(); //__Pig_make_a_sound__
pp->animalSound(); //__Pig_make_a_sound__

vector<Animal *> voa;
voa.push_back(new Pig());
voa.push_back(new Dog());
for (auto x: voa) {
x->animalSound(); //__Pig_make_a_sound__
cout << endl; //__Dog_make_a_sound__
}
return 0;
}

讨论 2

什么情况下使用 virtual ?或者说,有什么使用建议?

  • 某类作为父类,其派生类需要对函数进行重定义 override,要对需要重定义的函数前加 virtual。
  • 某类存在派生,那么它的析构函数 dtor 必须是 virtual 的,否则可能导致资源泄露。
  • non-virtual 函数可以确保当你使用父类指针时,一定能调用父类的方法(上述例子)。

另外,virtual 函数调用要通过虚表 virtual table 找到对应的函数指针,而 non-virtual 函数在编译的时候就已经确定了调用函数的地址。所以,调用函数的效率上 non-virtual 函数高一些。

讨论 3

总结:

  • virtual 可以实现多态,这是我们都知道的。
  • non-virtual 也可以实现多态特性,但是在进行指针调用时候无法访问到子类的正确方法上,这一点需要注意。
  • 所以,养成好习惯,父类需要重定义 override 的函数,就加上 virtual。