条款34 区分接口继承和实现继承
public继承可以细分为函数接口继承
和函数实现继承
。
实际使用的三种情况
- 派生类只继承基类的接口;
- 派生类继承基类的接口和实现, 而且可以重写继承的实现;
- 派生类继承基类的接口和实现, 并且不允许重写任何东西;
例子
class Shape
{
public:
virtual void draw() const = 0;
virtual void error(const string& msg);
int objectID() const;
...
};
class Rectangle: public Shape { ... };
class Ellipse: public Shape { ... };
Shape::draw()
纯虚函数,目的是为了让派生类只继承其接口。
Shape::error()
非纯虚函数, 目的是让派生类继承该函数的接口和缺省实现。派生类可以重写缺省实现。
Shape::objectID()
非虚函数,目的是让派生类继承该函数的接口和一份强制性实现。
了解了纯虚函数,非纯虚函数,非虚函数的区别,就容易在设计时做出选择。
设计时,应避免2种常见的错误:
- 将类所有函数都是声明为非虚函数。
- 将类所有函数都是声明为虚函数。
这两种极端情况有时是合理的。对于第一种情况是:不想将类设计为基类,对于第二种情况是:将类设计为一个接口类。
补充说明:
- 纯虚函数也可以有实现。调用时需明确指定类名。
- 非纯虚函数提供默认实现是危险的。派生类如果忘记重写,将导致错误。
【解决办法】:将非虚函数定义为纯虚函数,利用纯虚函数的实现提供默认实现。这样派生类必须要重写函数,只需要调用基类纯虚函数的实现。
这个解决办法是从基类的实现的角度设计,避免派生类实现和使用上出现错误。
参考
《Effective C++》