Guru of the Week 条款07:编译期的依赖性

原创 2001年10月23日 20:13:00
 

GotW #07 Compile-Time Dependencies

著者:Herb Sutter     

翻译:kingofark

[声明]:本文内容取自www.gotw.ca网站上的Guru of the Week栏目,其著作权归原著者本人所有。译者kingofark在未经原著者本人同意的情况下翻译本文。本翻译内容仅供自学和参考用,请所有阅读过本文的人不要擅自转载、传播本翻译内容;下载本翻译内容的人请在阅读浏览后,立即删除其备份。译者kingofark对违反上述两条原则的人不负任何责任。特此声明。

Revision 1.0

Guru of the Week 条款07:编译期的依赖性<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

 

难度:7 / 10

 

(大多数程序员使用#include包含的头文件都比实际需要的多。你也是这样的吗?想知道的话,请看本条款。)

 

 

[问题]

 

[注意:这个问题比想象的还要难!下面程序中的注释都是非常有用的。]

 

大多数程序员使用#include包含的头文件都比实际需要的要多。这会严重的影响并延长程序的建立时间(build time),特别是当一个被频繁使用的头文件中包含了太多其它的头文件的时候,问题越发严重。

 

首先,在下面的头文件当中,有哪些#include语句可以在不对程序产生副作用的情况下被直接去掉?其次,还有哪些#include语句可以在对程序进行适当的修改之后被去掉?程序将如何修改?(你不能改变X类和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

                // (注意: 只有AC有虚拟函数(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_;

    };

 

 

[解答]

 

首先,我们考虑那些可以被直接去掉的#include语句(或者说是头文件)。为了便于查看,我们再把原始的代码列在下面:

 

    // gotw007.h (其实现文件为gotw007.cpp)

    //

    #include "a.h"  // class A

    #include "b.h"  // class B

    #include "c.h"  // class C

    #include "d.h"  // class D

                    // (注意: 只有AC有虚拟函数(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.  我们可以直接去掉的头文件有:

 

l         iostream,因为程序里尽管用到了流,但并没有用到iostream里特定的东西。

l         ostreamsstream,因为程序中的参数和返回类型被前置声明(forward-declared)是可以的,所以其实只需要iosfwd就够了(要注意,并没有与iosfwd相对应的诸如stringfwd或者listfwd之类的标准头文件;iosfwd是考虑到向下兼容性问题的产物,它使得以前那些不支持模板的流子系统的代码仍然可用而不需要修改或者重写。)

 

我们不能直接去掉的头文件有:

 

l         a.h,因为AX的基类。

l         b.h,因为BY的基类。

l         c.h,因为现有的许多编译器需要list<C>能够看见对C的定义(这些编译器应该在未来的版本中修正这一点。)

l         d.hliststring,因为X需要知道Dstring的大小,XY都需要知道list的大小。

 

 

其次,我们再来考查那些可以通过隐藏XY的实现细节来被去掉的#include语句(或者说是头文件):

 

2.  我们可以通过让XY使用pimpl_的方法来去掉d.hliststring(也就是说,其私有部分被一个指针代替,这个指针指向类型被前置声明(forward-declared)的实体对象),因为这时,XY都不再需要知道listD或者string的大小。这也使我们可以干掉c.h,因为这时在X::clist中的C对象只作为参数或者返回值出现。

 

重要的事项:即使ostream没有被定义,内联的operator<<也可能仍然保持其内联性并使用其ostream参数!这是因为你只在调用成员函数的时候才真正需要相应的定义;当你想接收一个对象,并只将其当成一个在其它函数调用时的参数而不做任何其它额外的事情时,你并不需要该函数的定义。

 

最后,我们来看一下那些可以通过其它微小的修改而去掉的头文件:

 

3.  我们注意到BYprivate基类,而且B没有虚拟函数(virtual function),因此b.h也是有可能被去掉的。有一个(也只有一个)主要的原因使我们在派生类的时候使用private继承,那就是想要重载(override)虚拟函数(virtual function)。如此看来,在这里与其让Y继承自B,还不如让Y拥有一个类型为B的成员。要去掉b.h,我们应该让Y的这个类型为B的成员存在于Y中隐藏的pimpl_部分。

 

[学习指导]:请使用pimpl_把代码的调用者与代码的实现细节隔离开来。

 

摘录自GotW的编码标准:

 

     封装(encapsulation)和隔离(insulation):

在声明一个类的时候,应避免暴露出其私有成员:

应该使用一个形如“struct Xxxxlmpl* pimpl_”的不透明的指针来存储私有成员(包括状态变量和成员函数),例如:

class Map {private: struct Maplmpl* pimpl_;};

Lakos96: 398-405; Meyers92: 111-116; Murray93: 72-74

 

4.  基于以下几个原因,我们目前还不能够对a.h动手脚A被用作public基类;A含有虚拟函数(virtual function),因而其IS-A关系可能会被代码的调用者所使用。然而我们注意到,XY两个类之间没有任何关系,因此我们至少可以把XY的定义分别放到两个不同的头文件中间去(为了不影响现有的代码,我们还应该把现有的头文件作为一个存根(stub),让其用#include包含x.hy.h)。如此以来,我们至少可以让y.h不用#include包含a.h,因为现在它只把A用作函数参数的类型,不需要A的定义。

 

 

综上所述,我们现在可以得到一个清爽的头文件了:

 

    //---------------------------------------------------------------

    // 新文件x.h: 只包含两个#include!

    //

    #include "a.h"  // class A

    #include <iosfwd>

 

    class C;

    class D;

 

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

        // 注意: 这里不需要ostream的定义!

 

 

    //---------------------------------------------------------------

    // 新文件y.h: 没有#include!

    //

    class A;

    class C;

 

    class Y {

    public:

        C  Function4( A );

    private:

        class YImpl* pimpl_;

    };

 

 

    //---------------------------------------------------------------

    // gotw007.h 作为存根包含两个#include,又通过x.h附带了另外两个#include)

    //

    #include "x.h"

    #include "y.h"

 

 

    //---------------------------------------------------------------

    // gotw007.cpp中的新结构... 注意:impl 对象应该在XY的构造函数中

    // new来创建,并在XY的析构函数中用delete来清除。

    // XY 的成员函数要通过pimpl_ 指针来访问数据

    //

    struct XImpl    // 是的, 我们可以用"struct" ,虽然前置声明的时候

    {               // 我们用了"class"

        std::string  name_;

        std::list<C> clist_;

        D            d_;

    }

 

    struct YImpl

    {

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

        B b_;

    }

 

最后说几句:到现在,X的使用者只需要用#include包含a.hiosfwd就可以了。而Y的使用者也只需要包含a.hiosfwd,即使后来为了更新代码而需要包含y.h并去掉gotw007.h,也照样是一行#include都不用多加。与原来的程序相比,这是多么大的改进呀!

mysql的week函数与JAVA计算周的差别问题

1、问题: 在某些情况下,会需要将日期按周来进行排序或统计,mysql就要用到week()或yearWeek()函数,就会发现,比如2016年的某一天,在mysql里面是属于第30周,但在JAVA中使...
  • cwfreebird
  • cwfreebird
  • 2017年01月22日 15:06
  • 1215

阿里巴巴北京实习 Week 1

7 月 28 号周一入职. # Mon 签协议, ling
  • snowonion
  • snowonion
  • 2014年08月02日 21:23
  • 850

HDU - 3743 Frosh Week(树状数组+离散化)

题意: 给你一个长度为n的序列(最长100万),每次只能交换相邻的两个数字,问最小交换多少次,能使得序列有序。 解析: 这道题的数据量很大,所以要找规律,最小的排序次数,就是这个序列的逆序数的...
  • HelloWorld10086
  • HelloWorld10086
  • 2015年03月08日 19:45
  • 447

九度OJ 1043:Day of Week(星期几) (日期计算)

时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:5349 解决:1923 题目描述: We now use the Gregorian style...
  • thudaliangrx
  • thudaliangrx
  • 2015年10月18日 18:05
  • 615

C++中星期几计算公式

蔡勒公式(Zeller):是一个计算星期的公式。 随便给一个日期,就可以使用这个公式推算出事星期几。 公式如下:Weeks = [C/4] - 2C + y + [y/4] + [13*(M +1)/...
  • u012307430
  • u012307430
  • 2016年11月30日 18:12
  • 279

GPS,UTC和本地时间的显示器

GPS,UTC和本地时间的显示器 GitHub仓库:https://github.com/XinLiGitHub/GpsUtcAndLocalTime PS:博文不再更新,后续更新会在GitHub仓库...
  • u012325601
  • u012325601
  • 2017年10月22日 18:43
  • 115

学习笔记:生成对抗网络(Generative Adversarial Nets)(附代码)

同时训练两个模型:(1)生成模型G,不断捕捉训练库里真是图片的概率分布,将输入的随机噪声转变成新的样本(即假数据),使其像是一个真的图片。(2)判别模型D,用来估计一个样本来自训练数据的概率,即它可以...
  • SusanZhang1231
  • SusanZhang1231
  • 2017年06月22日 14:52
  • 468

深入理解Stata的week()函数

Stata日期函数中week()并非真正意义上的一周,本文提到的方法可以解决这一问题。...
  • financexx
  • financexx
  • 2015年01月09日 14:22
  • 1962

week 10 的遗留问题

C程序设计Week10课上实例 分类: C课程 2014-05-06 16:28 65人阅读 评论(0) 收藏 举报 目录(?)[+] 一个神奇的程序变量r...
  • u013122105
  • u013122105
  • 2014年05月08日 17:31
  • 262

20—小结(Week)

遇到的一些问题的小结: 通过invalidateOptionsMenu();会更新菜单栏重写的@Override publicboolean onPrepareOptionsMenu(...
  • u013145194
  • u013145194
  • 2016年05月28日 22:57
  • 147
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Guru of the Week 条款07:编译期的依赖性
举报原因:
原因补充:

(最多只允许输入30个字)