【C++基础】第八十七课:[面向对象程序设计]抽象基类

纯虚函数,抽象基类

Posted by x-jeff on October 29, 2023

【C++基础】系列博客为参考《C++ Primer中文版(第5版)》C++11标准)一书,自己所做的读书笔记。
本文为原创文章,未经本人允许,禁止转载。转载请注明出处。

1.抽象基类

假设我们希望扩展书店程序并令其支持几种不同的折扣策略。除了购买量超过一定数量享受折扣外,我们也可能提供另外一种策略,即购买量不超过某个限额时可以享受折扣,但是一旦超过限额就要按原价支付。或者折扣策略还可能是购买量超过一定数量后购买的全部书籍都享受折扣,否则全都不打折。

上面的每个策略都要求一个购买量的值和一个折扣值。我们可以定义一个新的名为Disc_quote的类来支持不同的折扣策略,其中Disc_quote负责保存购买量的值和折扣值。其他的表示某种特定策略的类(如Bulk_quote)将分别继承自Disc_quote,每个派生类通过定义自己的net_price函数来实现各自的折扣策略。

在定义Disc_quote类之前,首先要确定它的net_price函数完成什么工作。显然我们的Disc_quote类与任何特定的折扣策略都无关,因此Disc_quote类中的net_price函数是没有实际含义的。

我们可以在Disc_quote类中不定义新的net_price,此时,Disc_quote将继承Quote中的net_price函数。

然而,这样的设计可能导致用户编写出一些无意义的代码。用户可能会创建一个Disc_quote对象并为其提供购买量和折扣值,如果将该对象传给一个像print_total这样的函数,则程序将调用Quote版本的net_price。显然,最终计算出的销售价格并没有考虑我们在创建对象时提供的折扣值,因此上述操作毫无意义。

1.1.纯虚函数

认真思考上面描述的情形我们可以发现,关键问题并不仅仅是不知道应该如何定义net_price,而是我们根本就不希望用户创建一个Disc_quote对象。Disc_quote类表示的是一本打折书籍的通用概念,而非某种具体的折扣策略。

我们可以将net_price定义成纯虚(pure virtual)函数从而令程序实现我们的设计意图,这样做可以清晰明了地告诉用户当前这个net_price函数是没有实际意义的。和普通的虚函数不一样,一个纯虚函数无须定义。我们通过在函数体的位置(即在声明语句的分号之前)书写=0就可以将一个虚函数说明为纯虚函数。其中,=0只能出现在类内部的虚函数声明语句处:

1
2
3
4
5
6
7
8
9
10
//用于保存折扣值和购买量的类,派生类使用这些数据可以实现不同的价格策略
class Disc_quote : public Quote {
public:
	Disc_quote() = default;
	Disc_quote(const std::string& book, double price, std::size_t qty, double disc) : Quote(book, price), quantity(qty), discount(disc) { }
	double net_price(std::size_t) const = 0;
protected:
	std::size_t quantity = 0; //折扣适用的购买量
	double discount = 0.0; //表示折扣的小数值
};

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

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

含有(或者未经覆盖直接继承)纯虚函数的类是抽象基类(abstract base class)。抽象基类负责定义接口,而后续的其他类可以覆盖该接口。我们不能(直接)创建一个抽象基类的对象。因为Disc_quote将net_price定义成了纯虚函数,所以我们不能定义Disc_quote的对象。我们可以定义Disc_quote的派生类的对象,前提是这些类覆盖了net_price函数:

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

Disc_quote的派生类必须给出自己的net_price定义,否则它们仍将是抽象基类。

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

接下来可以重新实现Bulk_quote了,这一次我们让它继承Disc_quote而非直接继承Quote:

1
2
3
4
5
6
7
8
9
//当同一书籍的销售量超过某个值时启用折扣
//折扣的值是一个小于1的正的小数值,以此来降低正常销售价格
class Bulk_quote : public Disc_quote {
public:
	Bulk_quote() = default;
	Bulk_quote(const std::string& book, double price, std::size_t qty, double disc) : Disc_quote(book, price, qty, disc) { }
	//覆盖基类中的函数版本以实现一种新的折扣策略
	double net_price(std::size_t) const override;
};