当你在设计一个 class 的继承体系时,对于基类的成员函数,你可能会希望 Derived class :
- 只继承基类成员函数的接口
- 能够同时继承基类成员函数的接口和实现,但是又希望能够 override
- 能够同时继承基类成员函数的接口和实现,且不允许 override
这三种不同的基类成员函数实现方式,实际上就对应了基类成员函数的三种不同形式:pure virtual function,impure virtual function 和 non-virtual function。假设现在有如下的继承体系,利用这个继承体系对这三种不同的成员函数类型作详细说明。
class Shape
{
public:
virtual void draw() const = 0;
virtual void error(const std::string& msg);
int objectID() const;
}
class Rectangle : public Shape { ... };
class Ellipse: public Shape { ... };
pure virtual function
- Shape 中拥有一个 pure virtual function
draw
,这使得 Shape 成为了一个 abstract class,客户端不能够创建 Shape 的实体。 - pure virtual function 最重要的两个特点就是:它必须被任何继承了它们的 Derived class 重写,而且它们在 Base class 中通常没有定义。也就是说,在 Base class 中声明一个 pure virtual function,目的就是让 Derived class 只继承这个函数接口。
impure virtual function
同 pure virtual function 一样,Derived class 会继承其函数接口,同时可以 override,但不同的是,impure virtual function 函数会提供一份缺省的实现代码。也就是说,在 Base class 中声明一个 impure virtual function,目的就是让 Derived class 继承这个函数接口和一份缺省的实现。
但是,Derived class 获得这样一份缺省的实现,有时会带来一些问题。比如下面这种情形,航空公司目前只拥有 A 和 B 两种飞机机型,而且两者以相同的方式飞行:
class Airplane
{
public:
virtual void fly(const Airport& destination);
//...
};
void Airplane::fly(const Airport& destination)
{
//缺省实现
}
class ModelA : public Airplane { //... };
class ModelB : public Airplane { //... };
为了表示所有的飞机能飞,并且不同的飞机原则上需要有不同的 fly 实现,所有 fly 函数被声明为 virtual 函数。由于目前拥有的 A 和 B 机型的飞行方式相同,为了避免代码重复,所以将 fly 声明为 impure virtual fucntion,在 Airplane::fly 中给了一份缺省的实现,同时被 ModelA 和 ModelB 继承。
那么现在,假设,航空公司又购买了一种新的 C 机型,它的飞行方式和 A 与 B 不同。此时,如果新添加的子类 ModelC 忘记了重写 fly 函数 (实际上也很有可能发生),这个时候 ModelC 就会获得 Airplane::fly 的一份缺省的实现,这份实现并不适用于 C 机型,这个时候就产生了错误。
其实这个问题的关键,并不在于 Airplane::fly 中提供了缺省的实现,而且子类 ModelC 并没说明自己要那一份缺省的实现,Airplane::fly 就给它了。所以这里,我们要解决的问题就是,如果子类明确提出了 “要一份缺省的实现”,那么 Base class 再给它,否则就不应该给。解决方法也不难,就是要 切断 virtual 函数的接口和缺省实现之间的连接,可以像下面这样:
class Airplane
{
public:
virtual void fly(const Airport& destination) = 0;
//...
protected:
void defaultFly(const Airport& destination);
};
void Airplane::defaultFly(const Airport& destination)
{
//缺省实现
}
像上面这样,将缺省的行为移到一个单独的函数之中,并将它设置为 protected 以及 non-virtual,供子类继承而又不对外开放。这样的话,如果子类需要这样一份缺省的实现,就可以直接在 fly 函数中对 defaultFly 作一个 inline 的调用:
class ModelA : public Airplane
{
public:
virtual void fly(const Airport& destination)
{
defaultFly(destination);
}
};
class ModelB : public Airplane
{
public:
virtual void fly(const Airport& destination)
{
defaultFly(destination);
}
};
class ModelC : public Airplane
{
public:
virtual void fly(const Airport& destination)
{
//defaultFly(destination); 不要这个缺省的实现
//... 重新实现
}
}
现在 ModelC 就不可能意外获得那样一份缺省的实现了,因为它自己必须重写 fly 函数。
此时,又有人会担心,fly 和 defaultFly 名称过于雷同而引起 class 命名空间污染问题。实际上,我们也可以利用:“pure virtual function 必须在 derived class 中重写,但是在 base class 中也可以拥有自己的实现” 这样一个事实:
class Airplane
{
public:
virtual void fly(const Airport& des) = 0;
};
void Airplane::fly(const Airport& des)
{
//缺省实现
}
class ModelA : public Airplane
{
public:
virtual void fly(const Airport& destination)
{
Airplane::fly(destination);
}
};
class ModelB : public Airplane
{
public:
virtual void fly(const Airport& destination)
{
Airplane::fly(destination);
}
};
class ModelC : public Airplane
{
public:
virtual void fly(const Airport& destination)
{
//... 重新实现
}
}
non-virtual function
- 如果成员函数是一个 non-virtual function,说明,它并不打算在 Derived class 中有不同的行为,无法被 Derived class override。也就是说,声明 non-virtual function 的目的就是为了让 Derived class 继承函数接口和一份强制性实现。