分享
 
 
 

Guru of the Week #7:编译时期的依赖关系(Compile-time Dependences)

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

作者:Herb Sutter

译者:plpliuly

/*此文是译者出于自娱翻译的GotW(Guru of the Week)系列文章第7篇,原文的版权是属于Herb Sutter(著名的C++专家,"Exceptional C++"的作者)。此文的翻译没有征得原作者的同意,只供学习讨论。——译者

*/

#7 编译时期的依赖关系(Compile-Time Dependencies)

难度:7/10

很多人在写C++程序时都会用#include包含一些没有必要的头文件。你是否也是这样呢?看看我们下面要讨论的问题就知道了。

问题:

[提醒:这个问题会比看起来要难。另外请注意程序中的注释。]

包含过多不必要的头文件会严重增长程序build时间,尤其是在一个被很多源文件包含的头文件中#include了太多的头文件。

在下面的头文件中,哪些#include语句可以立即去除掉?第二,哪些#include语句可以在作适当调整后去除?(你不能改变class X和class Y的公共接口;也就是说,你做的任何更改不能影响客户代码)。

// gotw007.h (implementation file is gotw007.cpp)

//

#include "a.h" // class A

#include "b.h" // class B

#include "c.h" // class C

#include "d.h" // class D

// (note: only A and C have virtual functions)

#include <iostream>

#include <ostream>

#include <sstream>

#include <list>

#include <string>

class X : public A {

public:

X ( const C& );

D Function1( int, char* );

D Function1( int, C );

B& Function2( B );

void Function3( std::wostringstream& );

std::ostream& print( std::ostream& ) const;

private:

std::string name_;

std::list<C> clist_;

D d_;

};

std::ostream& operator<<( std::ostream& os, const X& x )

{ return x.print(os); }

class Y : private B {

public:

C Function4( A );

private:

std::list<std::wostringstream*> alist_;

};

答案

首先,看看哪些头文件完全没必要包括进来.

1。我们可以立即去掉:

-iostream,因为尽管用了streams,但并没有用到任何iosteam中任何特殊的东西。

-osteam和sstream,因为参数和返回值都只需用前递类型声明(forward-declared)就可以了。因此只需包括iosfwd就可以了.(注意没有对应的"stringfwd"或"listfwd"标准库头文件;iosfwd是为了向下兼容已有的使用非模板stream子系统的代码)

我们不能立即去除的头文件有:

-a.h:因为A是X的基类

-b.h:因为B是Y的基类

-c.h:因为很多现有的编译器要求在实例化list<C>时知道C的定义(在以后的编译器版本中应该修正这点)

-d.h:list和string,因为X类需要知道D和string的对象大小,而且X和Y都需要知道list的对象大小。

然后,让我们看看哪些头文件可以在做适当调整后去掉:

2。我们可以通过让X和Y使用pimpl_来去处d.h,list和string(也就是说,私有部分用指向实现对象的指针代替,实现对象是前递声明(forward-declared)的),因为这样X和Y就不需要知道D和list,string的大小信息了。这也使得我们可以不需包括c.h,因为在X::clist_中C的对象只作为参数或者返回值出现。

重要的提示:定义为内联类型的操作符<<可以保持为内联的并使用ostream类型的参数,尽管ostream还没有定义!这是因为你只在要调用成员函数的时候才需要参数的定义,而当你只是需要用一个对象来作为函数的参数时并不需要知道该对象的定义。

最后,看看是否可以继续调整以去除更多的头文件:

3。注意到B是Y的私有基类而且B没有虚函数,我们又可以去除b.h。通常选择私有继承而不选择类聚合或包容(嵌套类)的主要原因就是为了重新定义基类的虚函数。因此,此处应该改为让Y有一个B类型的成员变量来代替从B类私有继承。为了去除b.h,这个成员变量应该是在Y的被隐藏的pimpl_部分。

[建议]尽量用pimpl_(也就是指向实现的指针)将实现细节和客户代码隔离开来。

下面的文字是从GotW 的代码规范中摘录出来的:

-封装和隔离

-避免在类的声明中直接定义私有成员

-使用一个非透明指针声明为“struct XxxxImpl* pimpl_”来存储私有成员(包括私有成员变量和成员函数),比如,class Map{

private:struct MapImpl* pimpl_;}(Lakos96:398-405;Meyer92:111-116;Murray93:72-74)

4.目前我们还不能对a.h做任何动作,因为A被用来做X的public基类,而且A有虚成员函数,因此客户代码中需要知道或使用这种IS-A关系。然而,注意到X和Y根本不相关,我们可以将X和Y的定义分离到两个不同的头文件中(假设把现在这个头文件改成一个包括x.h和y.h的存根,因此就不需要改变现有的代码)。这样的话,至少y.h不需要包括a.h,因为它只是使用了A作为函数参数,那是不需要定义的。

把上述内容综合起来,我们可以得到如下几个更加信息的头文件:

file://---------------------------------------------------------------

// new file x.h: only TWO includes!

//

#include "a.h" // class A

#include <iosfwd>

class C;

class D;

class B;//译者加

class X : public A {

public:

X ( const C& );

D Function1( int, char* );

D Function1( int, C );

B& Function2( B );

void Function3( std::wostringstream& );

std::ostream& print( std::ostream& ) const;

private:

class XImpl* pimpl_;

};

inline std::ostream& operator<<( std::ostream& os, const X& x )

{ return x.print(os); }

// NOTE: this does NOT require ostream's definition!

file://---------------------------------------------------------------

// new file y.h: ZERO includes!

//

class A;

class C;

class Y {

public:

C Function4( A );

private:

class YImpl* pimpl_;

};

file://---------------------------------------------------------------

// gotw007.h is now just a compatibility stub with two lines, and

// pulls in only TWO extra secondary includes (through x.h)

//

#include "x.h"

#include "y.h"

file://---------------------------------------------------------------

// new structures in gotw007.cpp... note that the impl objects

// will be new'd by the X/Y ctors and delete'd by the X/Y dtors

// and X/Y member functions will access the data through their

// pimpl_ pointers

//

struct XImpl // yes, this can be called "struct" even

{ // though the forward-decl says "class"

std::string name_;

std::list<C> clist_;

D d_;

}

struct YImpl

{

std::list<std::wostringstream*> alist_;

B b_;

}

从上面的几个头文件中可以看出,使用X的客户代码(源文件)只需包含a.h和iostwd;在不改变客户代码情况下,使用Y的客户代码也只需要包含a.h和iosfwd,如果日后使用Y的客户代码更新将包含gotw007.h改为包含y.h,那Y的客户代码就不需要为使用Y而包含任何别的头文件。比较一下原来的头文件,这是一个多大的改进!

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有