Fork me on GitHub

虚函数与抽象基类

虚函数

在C++语言中,当我们使用基类的引用或指针调用一个虚成员函数时会执行动态绑定。因为我们知道运行时才能知道到底调用了那个版本的虚函数,所以所有的虚函数都必须有定义。通常情况下,如果我们不适用某个函数,则无须为该函数提供定义。但是我们必须为每一个虚函数都提供定义,而不管它是否被用到了,这是因为编译器也无法确定到底会使用那个虚函数。

对虚函数的调用可能在运行时才被解析

当某个虚函数通过指针或引用调用时,编译器产生的代码直到运行时才能确定应该调用哪个版本的函数。被调用的函数是与绑定到指针或引用上的对象的动态类型相匹配的哪一个。

必须要搞清楚一点是,动态绑定只有当我们通过指针或引用调用虚函数时才会发生。

1
2
base = derived; //把derived的Quote部分拷贝给base
base.net_price(20); //调用Quote::net_price

注意: 当且仅当对通过指针或引用调用虚函数时,才会在运行时解析该调用,也只有在这种情况下对象的动态类型才有可能与静态类型不同。

派生类中的虚函数

当我们在派生类中覆盖某个虚函数时,可以再一次使用virtual关键字指出该函数的性质。然而这么做并非必须,因为一旦某个函数被声明成虚函数,则在所有派生类中它都是虚函数。

一个派生类的函数如果覆盖了某个继承而来的虚函数,则它的形参类型必须与被它覆盖的基类函数完全一致。

同样,派生类中虚函数的返回类型也必须与基类匹配。该规则存在一个例外,当类的虚函数返回类型是类本身的指针或引用时,上述规则无效。

final和override说明符

override

在类的设计中常常会用到虚函数。但是存在一个问题,在派生类中本来是要重写基类中的虚函数的时候,由于写错参数类型,写错函数名等问题,造成重新定义了一个函数,这往往会造成意想不到的错误。想要调试并发现这样的错误显然非常困难,c++11引入关键字override。在派生类中重写了虚函数以后,可以使用override来修饰重写的虚函数,这个时候如果基类中没有这个虚函数,此时编译器将报错。

1
2
3
4
5
6
7
8
9
10
11
struct B{
virtual void f1(int) const;
virtual void f2();
void f3();
};
struct D1:B{
void f1(int) const override; //正确:f1与基类中的f1匹配
void f2(int) override; //错误:B没有形如f2(int)的函数
void f3() override; //错误:f3不是虚函数
void f4() override; //错误:B没有名为f4的函数
};

final

在设计基类的时候,有时候我们不想后续的的派生类覆盖某个方法,这个时候,我们可以将这个方法修饰为 final 。派生类中任何试图覆盖该函数的行为都将导致错误。

1
2
3
4
5
6
7
8
9
struct D2:B{
//从B继承f2()和f3(),覆盖f1(int)
void f1(int) const final; //不允许后续的其他类覆盖f1(int)
};
struct D3:D2 {
void f2(); //正确:覆盖从间接基类B继承而来的f2
void f1(int) const; //错误:D2已经将f2声明成final
};

虚函数与默认实参

虚函数也可以拥有默认实参。如果某次函数调用使用了默认实参,则该实参值由本次调用的静态类型决定。

换句话说,如果我们通过基类的引用或指针调用函数,则使用基类中定义的默认实参,即使实际运行的是派生类中的函数版本也是如此。

建议:如果虚函数使用默认实参,则基类和派生类中定义的默认实参最好一致。

回避虚函数的机制

在某些情况下,我们希望对虚函数的调用不要进行动态绑定,而是强迫其执行虚函数的某个特定版本。使用作用域运算符可以实现这一目的

1
2
//强行调用基类中定义的函数版本而不管baseP的动态类型到底是什么
double undiscounted = baseP->Quote::net_price(42);

该代码强行调用Quote的net_price函数,而不管baseP实际指向的对象类型到底是什么。该调用将在编译时完成解析。

抽象基类

纯虚函数

和普通的虚函数不一样,一个纯虚函数无须定义。我们通过在函数体的位置(即在声明语句的分号之前)书写=0就可以将一个虚函数说明为纯虚函数。其中,=0只能出现在类内部的虚函数声明语句处。

值得注意的是,我们也可以为纯虚函数提供定义,不过函数体必须定义在类的外部。也就是说,我们不能再类的内部为一个=0的函数提供函数体。

含有纯虚函数的类是抽象基类

含有(或者未经覆盖直接继承)纯虚函数的类是抽象基类(abstract base class)。抽象基类负责定义接口,而后续的其他类可以覆盖该接口。我们不能(直接)创建一个抽象基类的对象。

1
2
3
//Disc_quote声明了纯虚函数,而Bulk_quote将覆盖该函数
Disc_quote discounted; //错误:不能定义Disc_quote的对象
Bulk_quote bulk; //正确:Bulk_quote中没有纯虚函数

note: 我们不能创建抽象基类的对象。

派生类构造函数只初始化它的直接基类

先初始化基类的构造函数、再初始化自己的构造函数,最后执行构造函数体部分。

-------------本文结束感谢您的阅读-------------

本文地址:http://www.wangxinri.cn/2017/11/23/虚函数与抽象基类/
转载请注明出处,谢谢!

梦想夹带眼泪,咸咸的汗水!