条款32:确定你的public继承塑模出is-a关系
总结:
"public继承"意味着 is-a,适用于base class身上的每一件事情也一定适用于derived class身上,
因为derived 也是一个 base.
1、常规public继承
class Person {};
class Student:public Person {};
Public 继承下:每个学生一定是人 Student is a Person ,但是每个人不一定是学生。
2、鸟、会飞的鸟、企鹅
出现问题:
class Bird {
public:
virutal void fly();// 鸟可以飞
};
class Penguin:public Bird{}; // 企鹅是一种鸟
企鹅是一种鸟,但企鹅不会飞。我们想要的是企鹅包含鸟大部分特性,但是不能包括会飞这个成员函数。
解决办法:
1、
// 鸟
class Brid{};
// 会飞的鸟
class FlyingBird:public Bird{
public:
virtual void fly();// 成员函数
};
// 企鹅
class Penguin:public Bird{
};
问题:没必要专门区分会飞的鸟和不会飞的鸟。
2、为企鹅重新定义fly函数,令他产生一个运行期错误。
专门针对企鹅的fly()函数,定义运行错误。
void error (const string& msg);
class Penguin:public Bird{
// 专门针对企鹅的fly()函数,定义运行错误
virtual void fly() {error("Attempt to make a penguin fly"); }
};
问题:只有在运行期才能检测出来。
3、 不给企鹅定义fly()函数
// 鸟
class Brid{}; // 没有声明fly函数
// 企鹅
class Penguin:public Bird{}; // 没有声明fly函数
penguin p;
p.fly(); // 错误!!!
在编译期拒绝企鹅飞行fly()。
条款33 避免遮掩继承而来的名称
总结:
1、derived class内同名的名称会遮掩base class内的名称
2、为了让被遮掩的名称重建天日,可使用using 声明式或转交函数(forwarding functions)
一、发现问题
class Base{
private:
int x;
public:
virtual void f1()=0;
virtual void f1(int);
virtual f2();
};
class Derived:public Base{
public:
virtual void f1();
void f4();
};
调用时
Derived d;
int x;
d.f1();//正确,调用Derived::f1
d.f1(x);//错误,Derived::f1遮掩了Base::f1
d.f2();// 正确,调用Base::f2
二、分析
同名函数的话Derived会遮掩Base内的函数,只留下Derived的函数可被使用。
Base::f1不再被Derived继承,只剩下Derived::f1。
由于Derived中没有重新定义f2,所以Base::f2仍然存在。
三、解决办法
1、using声明式(在Derived的public区域)
class Base{...};
class Derived:public Base{
public:
using Base::f1;//让Base内名为f1的所有东西在Derived作用域内都可见
virtual void f1();
void f4();
};
意味着:继承base并加上重载函数,同时又希望重新定义或者覆盖一部分,必须为原本会被覆盖的名称引入using声明式。
导致:Base内所有函数都能被看见。
Derived d;
int x;
d.f1();//正确,调用Derived::f1
d.f1(x);//正确,调用Base::f1
d.d4();
2、inline转交函数(forwarding function)
如果只想看见Base 内的某个函数,(Derived 只想继承那个无参版本的f1),using 声明式排不上用场(它会令所有被看见)
class Derived:private Base{// private继承关系,Derived中所有的东西都会变为private
public:
virtual void f1() {Base::f1();} //转交函数,隐式转为inline
};
调用时
Derived d;
int x;
d.f1();//成立,调用的Derived::f1
d.f1(x);//失败,Base::f1被隐藏
条款34 区分接口继承和实现继承
总结:
1、在public继承之下,derived class总是继承base class的接口
2、pure virtual 纯虚函数只具体指定接口函数
3、impure virtual 非纯虚函数具体指定接口继承及缺省实现继承
4、non-virtual 函数具体指定接口继承以及强制性实现继承
class Shape{
public:
virtual void draw() const=0; // 纯虚函数 pure virtual
virtual void error(const string& msg); // 非纯虚函数 impure virtual
int objectID() const; // 非虚函数 non-virtual
};
class Retangle: public Shape {};
class Ellipse: public Shape {};
Shape是个抽象class,它的pure virtual 函数draw()使他成为一个抽象class。
一、 pure virtual 纯虚函数只继承接口
class Shape{
public:
virtual void draw() const=0; // 纯虚函数 pure virtual
。。。。
}
声明纯虚函数目的:为了让derived class 只继承函数接口,在derived 中实现。
特性:1、被任何继承了他的derived class重新声明。2、在抽象class中没有定义。
Shape* ps=new Shape; // 错误,Shape是抽象类
Shape* ps1=new Retangle;// 基类指针ps1指向派生类对象
ps1->draw(); // 调用派生类成员函数Retangle::draw()
ps1->Shape::draw();// 调用基类成员函数Shape::draw()
二、impure virtual 非纯虚函数 继承接口和缺省实现
class Shape{
public:
virtual void error(const string& msg); // 非纯虚函数 impure virtual
。。。。
}
派生类中必须支持一个error函数,但如果不想自己写一个,也可以使用Shape class基类提供的缺省版本。
三、impure virtual 潜在危险
// 基类
class Airplane{
public:
vitual void fly(const string& destination);// 声明impure virtual函数
};
void Airplane::fly(const string& destination){// 定义impure virtual 函数,缺省函数
......
}
// 派生类AB,声明了fly函数
class ModelA:public Airplane{};
class ModelB:public Airplane{};
// 派生类C,没有声明fly函数
class ModelC:public Airplane{};
基类提供了缺省函数fly(),但是Model A和B都进行了重新定义fly()函数。
Model C没有声明fly函数,如果调用了fly函数,就会试图以Model A或B的方式来飞行。
Airplane* pa=new ModelC;
pa->fly();// 调用缺省实现
四、impure virtual 解决
问题不在于impure virtual有缺省行为,而是ModelC未说出我要的情况下就继承了该缺省行为。
解决:切断虚函数 接口和 缺省实现 之间的连接。
条款38 通过复合塑模出has-a 或根据某物实现出
总结:
1、复合和public继承完全不同
2、在应用域,复合意味着has-a(有一个),在实现域,复合意味着is-implemented-in-terms-of(根据某物实现出)。
如果说public继承是一种is-a的关系的话,复合就是has-a关系。
1、复合就是在一个类中has a其他类的对象作为自身成员变量,根据另一个对象实现出来。
class Address {};
class PhoneNumber {};
// 主要类
class Person{
public:
....
private: // private
string name;
Address address;// Address类的对象
PhoneNumber phonenumber;// PhoneNumber类的对象
};
public 继承是 is-a
复合是 has-a
Person有一个名称、一个地址、电话。
2、举例实现
set只是利用了list的某些特性,但是不是 is-a 的关系。不能用public继承实现。
template<class T>
class Set{
public:
bool member(const T& item) const;
void insert(const T& item);
private:
list<T> rep;// !!! 采用其他类的对象作为自身的成员变量 has a
};
成员函数实现起来,也是可以利用list的功能。
bool Set<T>::member(const T& item) const
{
return find(rep.begin(),rep.end(),item!=rep.end());
}
bool Set<T>::insert(const T& item)
{
if(!member(item)) rep.push_back(item);
}
到目前为止,大家应该能理解什么叫is-implemented-in-terms-of了吧,就是依据某物来实现,就像这里的set,就是依据于list来实现自身的结构的,这种情况下也用has-a复合模型。
条款39 明智而审慎地使用private继承
总结:
- 当你希望访问protected接口的时候,使用private继承比复合更好,因为private继承能够提供访问权限;
- 当你希望override它的virtual函数的时候,使用private继承更好,因为继承能够提供override。
- 除此之外一般来说,使用复合比使用private继承更好。
一、Private继承现象
class Person{...};
class Student:private Person{...};
1、private继承意味着“根据某物实现出”,只有实现部分被继承,接口部分应被忽略。
D类以私有private形式继承B类,意味着,D对象根据B对象实现而得,再没有其他意涵了。
2、 private继承而来的基类成员都会在派生类中成为private属性,纵使它们在base class中原本是protected或public属性;
3、尽可能使用复合,必要时再采用private继承。用private适用情况:一个想要成为derived class 想访问一个想成为base class的protected成分,或需要重新定义其一或多个virtual函数。
二、复合+Public继承的优点
private继承和复合都是“根据某物实现出”,在设计时应尽量使用复合,少使用private继承。
class Timer {
public:
explicit Timer(int freq);
virtual void onTick()const;//虚函数
};
class Widget{
private: // 1、类成员变量是private
class WidgetTimer:public Timer{ //2、public继承
public:
virtual void onTick() const;//3、虚函数,重新定义onTick()函数
};
WidgetTimer timer;// 4、复合,利用另一个对象
}
WidgetTimer是Widget内部的一个private成员并继承Timer,Widget的Derived class将无法取用WidgetTimer,重新定义其virtual函数。
1、private继承能够重新定义基类中的虚函数(即使不能调用),很多情况下是应该阻止的;而复合很容易控制成员的访问权限。
2、复合能够使编译依赖性降低。
条款40 明智而审慎地使用多重继承
总结:
1、virtual虚继承会增加大小、速度、初始化复杂度的成本,如果virtual base class 不带任何数据,将是最具有实用价值的情况。
2、多重继承比单一继承复杂,正确使用情况:其中一个涉及public继承,另一个是private继承某个协助实现的class的结合。
一、出现问题
class A {
public:
void check() {
cout << "A" << endl;
}
};
class B {
private:
void check() const {
cout << "B" << endl;
}
};
class C : public A, public B {
};
int main(){
C c;
c.check();//无法调用,为解决歧义必须显示指出调用哪个函数例如c.A::check();
}
二、分析
有时候基类和子类之间有一条以上的相通路线,则会导致基类中的数据被复制两份到子类中,解决方法是使用虚继承,避免继承得来的成员变量重复.
class File{...};
class InputFile: virtual public File {...};
class OutputFile: virtual public File{...};
class IOFile: public InputFile
public OutputFile
{...};
虚继承问题:,虚继承会增加大小、速度、初始化及赋值等成本。
virtual继承忠告:第一,非必要不要使用virtual bases;
第二,如果必须使用virtual bases,尽可能避免在其中放置数据。
三、解决办法
多重继承的确有正当用途。其中一个情节涉及“public继承某个interface class”(接口)和“private继承某个协助实现的class”(实现)的两相结合。例子:
class Iperson {
public:
virtual ~IPerson();
virtual std::string name() const=0;
virtual std::string birthDate() const=0;
};
class DatabaseID { ... }; //稍后被使用
class PersonInfo { //这个class有若干有用函数,可用以实现IPerson接口
explicit PersonInfo(DatabaseID pid);
virtual ~PersonInfo();
virtual const char* theName() const;
virtual const char* theBirthDate() const;
virtual const char* valueDelimOpen() const;
virtual const char* valueDelimClose() const;
};
const char* PersonInfo::valueDelimOpen() const {
return "["; //缺省的起始符号
}
const char* PersonInfo::valueDelimClose() const {
return "]"; //缺省的结尾符号
}
const char* PersonInfo::theName() const {
static char value[Max_Formatted_Field_Value_Length]; //保留缓冲区给返回值使用:static,自动初始化为“全0”
std::strcpy(value, valueDelimOpen()); //写入起始符号
... //将value内的字符串附到这个对象的name成员变量中
std::strcat(value, valueDelimClose()); //写入结尾符号
return value;
}
class CPerson : public Iperson, private PersonInfo { //注意,多重继承
public:
explicit Cperson(DatabaseID pid) : PersonInfo(pid) {}
virtual std::string name() const { //实现必要的IPerson成员函数
return PersonInfo::theName();
}
virtual std::string birthDate() const { //实现必要的IPerson成员函数
return PersonInfo::theBirthDate();
}
private:
const char* valueDelimOpen() const { //重新定义继承而来的virtual“界限函数”
return "";
}
const char* valueDelimClose() const {
return "";
}
};
Cperson和personInfo的关系是,PersonInfo刚好有若干函数可帮助Cperson比较容易实现出来。因此它们的关系是is-implemented-in-term-of(根据某物实现出)。这种关系可以两种技术实现:复合和private继承。一般复合比较受欢迎,但是在本例之中Cperson要重新定义valueDelimOpen()和valueDelimClose(),所以直接的解法是private继承。