首页 > 代码库 > Effective C++ 35,36,37

Effective C++ 35,36,37

35.使公有继承体现 “是一个” 的含义。

共同拥有继承意味着 “是一个”。如  class B:public A。 说明类型B的每个对象都是一个类型A的对象,A比B具有更广泛的概念。而B表示一个更特定的概念。

在C++中不论什么一个參数为基类的函数都能够实际取一个派生类的对象,仅仅有共同拥有继承会如此。对于共同拥有继承,如AB。若有两个函数 一个函数为 void fun1(A &a);还有一个函数为void fun2(B& b);则对于AB的两个对象a。和b。对于 fun1(a)和fun2(b和fun1(b))都是正确的。fun2(a)是错误的。注意仅仅有共同拥有继承才有这个特性,对于私有继承会与此不同。并且这是说 B的对象 "是一个“ A的对象,可是B的数组并非一个A的数组。

使用公有继承常常遇到的问题是对基类适用的规则并不适用于派生类。但公有继承又要求对基类对象适用的不论什么东西都适用于派生类对象,使用公有继承会导致一些错误的设计。

如对于企鹅与鸟,鸟是基类,其有嘴,翅膀等数据成员。另一个飞的成员函数virtual void fly();一開始你觉得鸟有一些属性。且鸟会飞,然后你有觉得企鹅公有继承于鸟,即企鹅是一种鸟,可是问题出现了,企鹅不会飞。

让企鹅直接公有继承于鸟类。这是一个错误的设计。所以你想去改进它,在依旧使用公有继承的前提下。

1.世上有非常多鸟不会飞,于是你将鸟类分成了两种,FlyingBird 和NoFlyingBird,分成会飞和不会飞两种鸟类,这两种鸟类都公有继承于鸟类,而企鹅公有继承于NoFlyingBird。

2.企鹅中依旧有fly()这个函数,可是又一次定义了这个fly函数。使之产生一个执行时错误,使企鹅是鸟,企鹅能飞,可是让企鹅飞的这个操作是错误的。

这是一个执行时才干检測的错误。

当利用一些知识和常识设计一些类并使用公有继承时,可是公有继承却没那么有效,由于最关键的问题是基类中的规则要相同适用于派生类对象,而我们想要用继承实现的对象却有两者不同的规则。而对于这种情况,一般要用”有一个“ 和”用。。

。来实现“这两种关系来实现。


36.区分接口继承和实现继承。

首先,接口是放在public中给外部调用的。而实现是隐藏在private中的内部逻辑。对于类的继承,有时希望派生类仅仅继承成员函数的接口。有时派生类同一时候继承函数的接口和实现。且同意派生类改写实现,有时派生类同一时候继承类的接口与实现。可是不同意改动不论什么东西。

纯虚函数必需要在详细实现类中又一次声明,它们在抽象类中往往未定义。定义纯虚函数的目的在于使派生类只 继承函数的接口。也就是第一种情况,这样的情况非常easy理解。

可是纯虚函数事实上是能够提供定义的。

对于另外一种和第三种情况,继承函数的接口和实现,一般使用虚函数来实现。

而须要改进的地方是。对于一个基类中的虚函数,其有一定的实现。而派生类能够继承这种接口和实现,既能够直接继承基类中这个接口,也能够重写这个接口的实现。

这样非常科学,可是又要一个问题,当一个新的派生类继承这个基类时。因为这个类中使用虚函数做接口,导致新的程序猿忘记了又一次声明这个虚函数并给予新的实现逻辑而去错误的使用虚函数中的默认逻辑而造成了错误。为了提供更安全的基类,使用纯虚函数做接口,让纯虚函数有自己缺省实现,在派生类继承时。直接调用基类纯虚函数的实现:

class A{
public:
	virtual void fun() const = 0;
};
void A::fun() const{
	cout<<"Class A"<<endl;
}
class B:public A{
public:
	virtual void fun() const;

};
void B::fun() const{
	A::fun();
}
如上所看到的,使用一个纯虚函数。可是带有缺省实现,而派生类继承时就必须又一次声明这个纯虚函数,而对于要调用基类的缺省实现时,除了上面直接调用基类的这个纯虚函数外,还能够通过在基类中的protected中设置一个默认的实现函数,如 void defaultFun() const。而派生类会继承这个默认实现,然后在派生类的又一次定义的虚函数中调用这个默认的实现函数就可以。

这个情况事实上就是另外一种情况,继承函数的接口和实现,且可以改动实现。一般使用虚函数。可是使用带默认操作的纯虚函数会更加安全。安全是一个非常重要的问题,假设不考虑安全性,非常多在Effective C++这本书中讨论的问题都是没有意义的,由于假设你明确之前程序的设定。就知道哪些事情该做,哪些事情不该做,就不会去犯一些错误,可是对于一个程序的开发。不是有一个人完毕的。当你理解自己的设定时,别人却不知道,维护你代码的人任意的做一些他们觉得应该可以做到的安全的事,却由于你之前考虑的不周全而使这些行为极度不安全。所以要认真考虑安全性的问题。写出尽可能完美安全的代码。

对于第三种情况,声明非虚函数。目的在于使派生类继承函数的接口和强制性实现,又因为不应该在派生类中又一次声明和定义基类的非虚函数,所以不会改动非虚函数的实现的。

所以,要理解纯虚函数,简单虚函数和非虚函数声明和功能上的差别。不用操心虚函数的效率问题,由于这真的是小问题,全部基类都应该虚函数。

一些函数不应该在派生类中又一次定义就要将其定义为非虚函数。


37.决不要又一次定义继承而来的非虚函数。

首先,对于又一次定义继承的非虚函数,称为对这个函数的隐藏。这是一种不经常使用的东西,正是由于有这个设定,绝不又一次定义继承而来的非虚函数。

这样做的原因也是非常easy理解的,也是多态的长处:

class A{
public:
	 void fun() const{
	cout<<"Class A"<<endl;
}
};
class B:public A{
public:
	 void fun() const{
	cout<<"Class B"<<endl;
}
};
int main(){
	B* b = new B();
	A* a = b;	
	b->fun();
	a->fun();
对于以上代码,对同一个对象,也就是b指向的对象,当将其转换为基类指针后,因为其为静态绑定的,其所指向的函数不同,获得了不同的结果。而多态时动态绑定,指向的函数通过虚指针指向同样的地址。
结论是,对于类B的对象,其又一次定义的函数fun()被调用时。其行为是不确定的,而决定因素与对象本身没有关系,而取决于指向它的指针的声明类型,引用也会和指针表现出这种异常行为,这种行为是不合理的。

而从理论上来考虑。对于公有继承意味着 ”是一个“,对于B中又一次定义了A中的实现后,B就不”是一个“ A了。



Effective C++ 35,36,37