Effective C++ 读书笔记9(32~34)

6 继承与面向对象设计

条款32:确定你的public集成塑模出is-a关系

请记住:

public继承意味is-a,适用于base classes深证的每一件事情也一定适用于derived classes身上,因为每一个derived class对象也都是一个base class对象


条款33:避免遮掩继承而来的名称

class Base{
private:
	int x;
public:
	virtual void mf1() = 0;
	virtual void mf2();
	void mf3();
};

class Derived: public Base{
public:
	virtual void mf1();
	void mf4();
};

void Derived::mf4(){
	
	mf2();
}
Base的作用域: mf1(1个函数);mf2(1个函数);mf3(1个函数);

Derived作用域:mf1(1个函数);mf4(1个函数);


对于mf4的实现,当编译器看到mf2,会查找各作用域,看看有没有某个名为mf2的声名式。首先查找local作用域,然后查找Derived作用域,然后查找base作用域。

看下边的例子,这次重载mf1和mf3,并添加新版mf3到Derived中去:

class Base{
private:
	int x;
public:
	virtual void mf1() = 0;
	virtual void mf1(int);
	virtual void mf2();
	void mf3();
	void mf3(double);
};

class Derived: public Base{
public:
	virtual void mf1();
	void mf3();
	void mf4();
};
Base作用域:mf1(2个函数);mf2(1个函数);mf3(2个函数);

Derived作用域:mf1(1个函数);mf3(1个函数);mf4(1个函数);


从名称观点来看,Base::mf1和Base::mf2不再被Derived继承:

Derived d;

int x;

d.mf1();

d.mf1(x); //错误,Derived::mf1遮盖了Base::mf1

d.mf2();

d.mf3();

d.mf3(x); //错误,Derived::mf3遮掩了Base::mf3

如你所见,上述规则依然适用,即使函数参数不同。

这些行为背后的理由是为了防止你在程序库或应用框架内建立新的derived类时附带的从base 类继承重载函数,但有时你通常会想继承重载函数。实际上如果你正在使用的public继承而又不继承那些重载函数,就是违反了is-a关系,那么该怎么办呢?可以使用using达到目标:

class Base{
private:
	int x;
public:
	virtual void mf1() = 0;
	virtual void mf1(int);
	virtual void mf2();
	void mf3();
	void mf3(double);
};

class Derived: public Base{
public:
	using Base::mf1;
	using Base::mf3;
	virtual void mf1();
	void mf3();
	void mf4();
};

Base作用域:mf1(2个函数);mf2(1个函数);mf3(2个函数);

Derived作用域:mf1(2个函数);mf3(2个函数);mf4(1个函数);


此时:

Derived d;

int x;

d.mf1();

d.mf1(x); //ok,调用Base::mf1

d.mf2();

d.mf3();

d.mf3(x); //ok,调用Base::mf3


请记住:

1.derived类内的名称会遮掩base类的名称。在public继承下没有人希望如此。

2.为了让被遮掩的名称重见天日,可以使用using声明式或转交函数(forwarding functions)


条款34:区分接口继承和实现继承

作为类设计者:

1.有时希望derived类只继承成员函数的接口;

2.有时希望继承函数的接口和实现,但又希望能够override所继承的实现;

3.有时希望derived class同时继承函数的接口和实现;


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{};

1.成员函数的接口总是会被继承。

Shape的三个函数类型不同,有什么样的暗示呢?

1.1 生命一个pure virtual函数的目的是为了让derived类只继承函数接口。

     令人意外的是,我们竟然可以为pure virtual函数提供定义,也就是说你可以为shape::draw提供一份实现代码,C++并不会发出怨言,但调用它的唯一途径是调用时明确指出其类名:

Shape* ps = new Shape;          //错误,shape是抽象的
Shape* ps1 = new Rectangle;
ps1->draw();
Shape* ps2 = new Ellipse;
ps2->draw();
ps1->Shape::draw();             //调用Shape::draw
ps2->Shape::draw();             //调用Shape::draw
1.2生命一般虚函数的目的,是让derived classes继承该函数的接口和缺省实现。

      但是,允许一般虚函数同时指定函数声明和缺省行为,却有可能造成危险:

class Airport{};
class Airplane{
public:
	virtual void fly(const Airport& destination);
	//...
};
void Airplane::fly(const Airport& destination){
	
	//default code, fly to destination
}

class ModelA: public Airplane{};
class ModelB: public Airplane{};

此处的fly函数为普通虚函数,有缺省的实现。A和B类型飞机如果不重写fly函数,就会继承之。如果又来了一个新款的C飞机,其实并不想按照缺省的实现飞行,但是又忘了自己写实现,那么就会继承缺省实现,但新款飞机用缺省实现飞出来的效果可能并不好。怎么办?

class Airplane{
public:
	virtual void fly(const Airport& destination) = 0;
	//...
protected:
	void defaultFly(const Airport& destination);
};

void Airplane::defaultFly(const Airport& destination){
	
	//default code, fly to destination
}

好了,若想使用缺省实现(A和B), 可以在其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);
	}
};

现在C不可能意外的继承不正确的fly实现代码了,因为AirPlane中的pure virtual函数迫使C必须提供自己的fly版本。

(如果把fly声明为pure的,那么所有的derived类都不能随意继承fly了,所有这里这样搞一下有意义吗?)

1.3 声明non-virtual函数的目的是为了令derived class继承函数的接口及一份强制性实现。


请记住:

1.接口继承和实现继承不同。在public继承之下,derived class总是继承base class的接口。

2.pure virtual函数只具体指定接口继承。

3.non-virtual函数具体指定接口继承及缺省实现继承。

4.non-virtual函数具体指定接口继承以及强制性实现继承。

你可能感兴趣的:(C++,框架,String,读书,Class,编译器)