栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

《Effective C++》总结篇(继承与面向对象设计)

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

《Effective C++》总结篇(继承与面向对象设计)

条款三十二:确定你的public继承塑模出is-a关系

如果你令class D(“Derived”)以public形式继承class B(“Base”),你便是告诉C++编译器(以及你的代码读者)说,每一个类型为D的对象同时也是一个类型为B的对象,反之不成立。你的意识是B比D表现出更一般化的概念,而D比B表现出更特殊化的概念。你主张“B对象可派上用场的任何地方,D对象一样可以派上用场”,因为每一个D对象都是一种B对象。反之如果你需要一个D对象,B对象无法效劳,虽然每个D对象都是一个B对象,反之并不成立。

每个学生都是人,但并非每个人都是学生,大概就是这种关系吧。

但这种关系就容易设计出奇怪的关系,比如鸟会飞,企鹅是鸟,但企鹅不会飞。如果你的鸟类有fly()的话,那你的企鹅类就会产生问题了!当然我们可以让其在调用企鹅的fly的时候抛出异常,这样就能使程序在运行期发生错误,但更好的设计是在编译器发生错误,即鸟不声明fly函数!

这就是public继承,当我们以public模塑base class和dervied class关系时,能够施行与base class对象身上的每件事,也可以在dervied class施行。编译器能通过编译,但是这并不一定代表程序行为一定正确(逻辑错误)

请记住:
“public继承”意味着 is-a,适用于base classes身上的每一件事情一定适用于derived classes身上,因为每一个derived class对象也都是一个base class 对象。

条款三十三:避免遮蔽继承而来的名称

C++的内层作用域的名称会遮掩外围作用域的名称。编译器会从local作用域开始查找,如果找到就不再找其他作用域(遮蔽),如果找不到就会找其外围作用域,直到往golbal作用域找去。

那么先看看这一段代码,当基类重载了mf1和mf3,并且添加了一个新版的mf3到Derived去,此时!
base class的所有名为mf1和mf3的函数都被derived class内的mf1和mf3函数遮掩掉了,从名称查找的观点来看,Base::mf1和Base::mf3不再被Derived继承了!!!!

#include 
using namespace std;

class Base
{
public:
	virtual void mf1() = 0;
	virtual void mf1(int) { cout << "Base mf1(int)" << endl; }
	virtual void mf2() { cout << "Base mf2()" << endl; }
	void mf3() { cout << "Base mf3()" << endl; }
	void mf3(double x) { { cout << "Base mf3("<
public:
	virtual void mf1() { cout << "Derived mf1()" << endl; }
	void mf3() { cout << "Derived mf3()" << endl; }
	void mf4() { cout << "Derived mf4()" << endl; }
};

int main()
{
	Derived d;
	int x = 10;
	d.mf1();//Derived mf1()
	//d.mf1(x);//错误,因为dervied::mf1遮掩了Base:mf1
	d.mf2();//Base mf2()
	d.mf3();//Derived mf3()
	//d.mf3(x);//错误,因为dervied::mf3遮掩了Base:mf3
	d.mf4();//Derived mf4()
	return 0;
}

因为你没有继承重载函数,使得base和derived classes之间不是is-a关系
但可以用using声明式达成目标:

#include 
using namespace std;

class Base
{
public:
	virtual void mf1() = 0;
	virtual void mf1(int) { cout << "Base mf1(int)" << endl; }
	virtual void mf2() { cout << "Base mf2()" << endl; }
	void mf3() { cout << "Base mf3()" << endl; }
	void mf3(double x) { { cout << "Base mf3("<
public:
	using Base::mf1;//让base class内名为mf1和mf3的所有东西在derived的作用域内可见。
	using Base::mf3;
	virtual void mf1() { cout << "Derived mf1()" << endl; }
	void mf3() { cout << "Derived mf3()" << endl; }
	void mf4() { cout << "Derived mf4()" << endl; }
};

int main()
{
	Derived d;
	int x = 10;
	d.mf1();//Derived mf1()
	d.mf1(x);//Base mf1(int)
	d.mf2();//Base mf2()
	d.mf3();//Derived mf3()
	d.mf3(x);//Base mf3(10)
	d.mf4();//Derived mf4()
	return 0;
}

有时候你不想继承base classes的所有函数,那你可以使用private继承。

#include 
using namespace std;

class Base
{
public:
	virtual void mf1() = 0;
	virtual void mf1(int) { cout << "Base mf1(int)" << endl; }
	virtual void mf2() { cout << "Base mf2()" << endl; }
	void mf3() { cout << "Base mf3()" << endl; }
	void mf3(double x) { { cout << "Base mf3("<
public:
	virtual void mf1() //转交函数
	{ 
		Base::mf1(); //暗自成为inline
	}
};

int main()
{
	Derived d;
	int x = 10;
	d.mf1();//Derived::mf1()
	d.mf1(x);//错误! Base::mf1被遮掩了
	return 0;
}

请记住:
derived classes内的名称会遮掩base classes内的名称。在public继承下从来没有人希望如此。
为了让被遮掩的名称再见天日,可使用using声明式或转交函数

条款三十四:区分接口继承和实现继承

public继承有两个部分组成:函数接口继承和函数实现继承。
于是有以下几条性质:
1.成员函数的接口总是被继承。
纯虚函数必须被任何”继承了它们”的具象类重新声明,而且它们在抽象的class中通常没有定义。

2.声明一个pure virtual函数的目的是为了让derived classes只继承函数接口。
并且具有纯虚函数的类称为抽象类,他不能被new出来。

#include 
using namespace std;

class Base
{
public:
	virtual void mf1() = 0;
};

int main()
{
	Base b = new b;
//错误
	return 0;
}

3.声明简朴的impure virtual函数的目的,是让derived classes继承该函数的接口和缺省实现。
但是有可能会出现继承类忘记重写impure virtual函数了,导致发生惨案!
所以有一个好的设计是使得基类声明为pure virtual,并且给一份默认缺省实现。

#include 
using namespace std;

class Base
{
public:
	virtual void mf1() = 0;
};
void Base::mf1() { cout << "Base::mf1();" << endl; }//缺省行为

class A :public Base
{
public:
	virtual void mf1() { Base::mf1(); }//使用默认的实现
};
class  B:public Base
{
public:
	virtual void mf1() { cout << "B::mf1()" << endl; }//自己重写实现
};

int main()
{
	A a;
	B b;
	a.mf1();//Base::mf1();      
	b.mf1();//B::mf1()
	return 0;
}

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

请记住:
接口继承和实现继承不同,在public继承之下,derived classes总是继承base class的接口。
pure virtual函数只具体指定接口继承
简朴的impure virtual函数具体指定接口继承及缺省实现继承。
non-virtual函数具体指定接口继承以及强制性实现继承

条款三十五:考虑virtual函数以外的其他选择

1.使用non-virtual interface(NVI)手法,那是Template Method设计模式的一种特殊形式,它以public non-virtual成员函数包裹较低访问性的virtual函数

#include 
using namespace std;

class Base
{
protected:
	virtual void DoFunc() { cout << "Base::DoFunc()" << endl; }
public:
	virtual void Func() { DoFunc(); }
};

class A :public Base
{
protected:
	virtual void DoFunc() { cout << "A::DoFunc()" << endl; }
};
class  B:public Base
{
public:
	virtual void DoFunc() { cout << "B::DoFunc()" << endl; }
};

int main()
{
	A a;
	B b;
	a.Func();
	b.Func();
	return 0;
}

2.将virtual函数替换为“函数指针成员变量”,这是策略模式的一种分解表现形式

#include 
using namespace std;
class Base;
void defaultFuc(const Base&);
class Base
{
public:
	typedef void (*DoFunc)(const Base&);
	Base(DoFunc func = defaultFuc):dofunc(func) {}
	void Func() const { dofunc(*this); }
private:
	DoFunc dofunc;
};

class A :public Base
{
public:
	A(DoFunc func = defaultFuc) :Base(func) {}
};
class  B:public Base
{
public:
	B(DoFunc func = defaultFuc) :Base(func) {}
};
void funcA(const Base&) { cout << "FuncA" << endl; }
void funcB(const Base&) { cout << "FuncB" << endl; }
int main()
{
	A a(funcA);
	B b(funcB);
	a.Func();
	b.Func();
	return 0;
}

3.以trl::function成员变量替换virtual函数,因而允许使用任何可调用物搭配一个兼容于需求的签名式。这也是策略设计模式的某种形式。

#include 
#include 
using namespace std;
class Base;
void defaultFuc(const Base&);

class Base
{
public:
	typedef std::tr1::function DoFunc;
	Base(DoFunc func = defaultFuc):dofunc(func) {}
	void Func() const { dofunc(*this); }
private:
	DoFunc dofunc;
};

class A :public Base
{
public:
	A(DoFunc func = defaultFuc) :Base(func) {}
};
class  B:public Base
{
public:
	B(DoFunc func = defaultFuc) :Base(func) {}
};
void funcA(const Base&) { cout << "FuncA" << endl; }
void funcB(const Base&) { cout << "FuncB" << endl; }
int main()
{
	A a(funcA);
	B b(funcB);
	a.Func();
	b.Func();
	return 0;
}

4.将继承体系内virtual函数替换为另一个继承体系内的virtual函数。这是策略模式传统实现手法.

#include 
#include 
using namespace std;
class Base;

class DoFunc
{
public:
	virtual void dofuc(const Base&) { cout << "DoFuc::dofuc()" << endl; }
};
class FuncA :public DoFunc
{
public:
	virtual void dofuc(const Base&) { cout << "FuncA::dofuc()" << endl; }
};
class  FuncB :public DoFunc
{
public:
	virtual void dofuc(const Base&) { cout << "FuncB::dofuc()" << endl; }
};
DoFunc defaultFuc;
class Base
{
public:
	Base(DoFunc* func = &defaultFuc):dofunc(func) {}
	void Func() const { dofunc->dofuc(*this); }
private:
	DoFunc* dofunc;
};
int main()
{
	FuncA A;
	FuncB B;
	Base a(&A);
	Base b(&B);
	a.Func();
	b.Func();
	return 0;
}

请记住:
virtual函数的替代方案包括NVI手法及策略设计模式的多种形式,NVI手法自身是一种特殊形式的模板方法设计模式。
将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员。
trl::function对象的行为就像一般函数指针。这样的对象可接纳“与给定之目标签名式兼容”的所有可调用物。

条款三十六:绝不重新定义继承而来的non-virtual函数

就是指如果继承类重新定义了继承而来的non-virtual就 发生违反is-a的关系。
看如下代码 :
A x对象分别被Base*,A*取了指针并调用Func结果却不相同。
毕竟它们调用的都说同一个对象x的Func。但是行为却不一样

#include 
using namespace std;
class Base
{
public:
	void Func() const { cout << "Base::Func()" << endl; }
};
class A :public Base
{
public:
	void Func()const { cout << "A::Func()" << endl; }
};
int main()
{
	A x;
	Base* pb = &x;
	A* pa=&x;
	pb->Func();
	pa->Func();
	return 0;
}

请记住:
绝对不要重新定义继承而来的non-virtual函数

条款三十七:绝不重新定义继承而来的缺省参数值

在C++中缺省参数值是静态绑定的。因为如果缺省值是动态绑定的话,编译器就必须有某种方法在运行期为virtual函数决定实当的参数缺省值。这比目前实行的”在编译器决定”的机制更慢而且更复杂。为了程序的执行速度和编译器实现上的简易度,C++做了这样的取舍。
A的Func(int x=1)的缺省值是1
而Base的缺省值是0
当执行pa->Func()的时候结果是A::Func() 0

#include 
using namespace std;
class Base
{
public:
	virtual void Func(int x=0) const { cout << "Base::Func() "<< x  << endl; }
};
class A :public Base
{
public:
	virtual void Func(int x=1)const { cout << "A::Func() "<< x << endl; }
};
int main()
{
	Base* pa=new A;
	pa->Func();
	return 0;
}

请记住:绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数–你唯一应该覆写的东西—却是动态绑定

条款三十八:通过复合塑模出has-a或“根据某物实现出”

复合是类型之间的一种关系,比如人有电话号码和家庭住址。
这种关系意味着has-a(有一个),通常用于处理两个不同领域。

#include 
using namespace std;
class PhoneNum
{
public:
	PhoneNum(int num) :num(num) {}
	int getNum()const { return num; }
private:
	int num;
};
class Address
{
public:
	Address(string adr) :adr(adr) {}
	string getAdr()const { return adr; }
private:
	string adr;
};
class Person
{
public:
	Person(PhoneNum pn,Address adr):pn(pn),adr(adr) {}
	void PrintInfo()const { cout << "PhoneNum="<< pn.getNum()<<"   Address="<
	PhoneNum pn(123456789);
	Address adr("地球");

	Person p(pn, adr);
	p.PrintInfo();
	return 0;
}

另外,像stl中的Set内部也是用到了list,Set的函数成员大量依赖list及标准程序库其他部分提供的机能来完成,即根据list实现出set

请记住:
复合的意义和public继承完全不同。
在应用域,复合意味has-a(有一个)。在实现域,复合意味is-implemented-in-terms-of(根据某物实现出)

条款三十九:明智而慎重的使用private继承

如果classes之间的继承关系是private,编译器不会自动将一个derived class对象转换为一个base class对象。由private base class继承而来的所有成员,在derived class中都会变成private属性,纵使它们在base class中原本是protected或public属性。

private继承意味implemented-in-terms-of(根据某物实现出)。如果D以private形式继承B,意思是D对象根据B对象实现 ,这在软件”设计”层面毫无意义,其意义只及于软件实现层面。
这和条款三十八说的复合的概念很像。但推荐尽可能使用复合。何时才用private?主要是当protected成员或virtual函数牵扯进来的时候。其实还有一种激进情况,那是当空间方面利害关系足以推翻private继承的支柱时。

假设我们需要用一个定时器去定时查看widget的数据。
我们有一个Timer类可以每秒执行Ontick函数,我们只需要让widget重新定义Ontick就行了。
但总不能public继承,毕竟这不是is-a的关系,很容易造成用户误用,所以我们以private继承Timer

#include 
#include
#include
using namespace std;
class Timer
{
public:
	void Start()
	{
		while (true)
		{
			Sleep(1000);
			OnTick();
		}
		
	}
	virtual void OnTick()const { cout << "Timer::OnTick()" << endl; }
};
class Widget:private Timer
{
public:
	Widget() { Start(); }
private:
	virtual void OnTick()const { cout << "OnTick()==>Widget INFO" << endl; }
};


int main()
{
	Widget w;
	return 0;
}

这是一个好设计,但private并非绝对必要,我们可以用复合取而代之。

#include 
#include
#include
using namespace std;
class Timer
{
public:
	void Start()
	{
		while (true)
		{
			Sleep(1000);
			OnTick();
		}
		
	}
	virtual void OnTick()const { cout << "Timer::OnTick()" << endl; }
};
class Widget
{
private:
	class WdigetTimer :public Timer
	{
	public:
		WdigetTimer() { Start(); }
		virtual void OnTick()const { cout << "OnTick()==>Widget INFO" << endl; }
	};
	WdigetTimer timer;
};


int main()
{
	Widget w;
	return 0;
}

然后就是private继承不会产生额外的开销。
我们都知道一个空类对象也会被强行塞一个char到空对象内。
但如果private继承的话,会有EBO(empty base optimilazation 空白基类最优化)。

#include 
using namespace std;
class B
{

};
class A:private B
{
	int x;
};
class C
{
	int x;
	B b;
};
int main()
{
	cout << sizeof(A) << "  " << sizeof(int)<<"  "< 

请记住:
private继承意味is-implemented-in-terms-of(根据某物实现出)。它通常比复合的级别低。但是当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的。
和复合不同,private继承可以造成empty base最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,很重要!

条款四十:明智而审慎地使用多重继承

钻石继承以及解决方案。virtual base class(virtual继承)
可以参考我写的这一篇
private继承意味着implement-in-terms-of(根据某物实现出)。
public inheritance(公开继承)意味着“is-a”(是一种)的关系。

所以我们应该public继承自接口,private继承自实现

请记住:
多重继承比单一继承复杂。它可能导致新的歧义性,以及对virtual继承的需要。
virtual 继承会增加大小,速度,初始化(及赋值)复杂度等等成本。如果virtual base classes不带任何数据,将是最具有实用价值的情况。
多重继承的确有正当用途。其中一个情节涉及public 继承某个interface class 和private继承某个协助实现的class的两相组合

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/935489.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号