最大程度降低编译期依赖性(读书笔记)

时间:2014.06.05

地点:基地

--------------------------------------------------------------------------------

一、识别多余的头文件

  习惯性地使用#include指令包含一些不必要的头文件会严重降低编译效率,尤其是砸常用头文件中包含过多其它头文件时。比如:

//x.h  这是实现类X的头文件
//
#include<iostream>
#include<ostream>
#include<list>
//A,B,C,D,E都不是模板,A和C中定义了虚函数
#include "a.h"
#include "b.h"
#include “c.h"
#include "d.h"
#include "e.h"
class X:public A,private B
{
  public:
    x(const C&);
    B f(int,char*);
    C f(int,C);
    C& g(B);
    E h(E);
    virtual std::ostream& print(std::ostream&) const;
private:
   std::list<C>clist_;
   D  d_;
};
inline std::ostream& operator<<(std::ostream& os,const X& X)
{
   return x.print(os);
}

1.1去掉#include<iostream>

这里在X类的声明中确实用到了流,但这里用到的流并不是iostream中的某个特殊声明,在X类中,最多只需要ostream就够了。也就是说永远不要用#include包含不必要的头文件

1.2用iosfwd代替ostream

我们还可以进一步压缩头文件

对应函数的参数类型和返回值类型而言,我们只需要前置声明一下就可以了,于是在这里,真正需要的只是ostream的前置声明,而无需它的完整定义,因为这里确实仅仅涉及到ostream类当成函数参数类型和函数返回类型的一个声明,并不需要它的定义。但于这里,我们又不能简单的使用class ostream来代替#include<iostream>,因为:

a.ostream属于命名空间std,我们不能在std之外声明std中的类型。

b.ostream其实是一个模板的类型定义(由typedef得来的,即:typedef biasic_ostream<char> ostream)。

正因为诸如ostream其实是一个模板实例,模板的前置声明会给代码带来混乱,我们也不可能可靠地对模板进行前置声明,因为在库的实现中还可能包含C++标准之外的一些工作,比如额外的模板参数,这便是不允许对std名字空间中的类型进行声明的主要原因之一。

然而好在标准库中提供了头文件iosfwd,该文件包含了所有流模板(包括basic_ostream)以及这些模板类型定义(ostream)的前置声明,于是可用#include<iosfwd>代替#include<ostream>

如果只需要流的前置声明,优先使用#include<iosfwd>

我们再来看那个内联函数:

inline std::ostream& operator<<(std::ostream& os,const X& x)
{
  return x.print(os);
}
这个内联函数中,参数类型和返回类型都是ostream& ,正如上面所说,显然不要ostream的定义,然后ostream& 参数将作为另一个函数的参数供它调用,这一步也是无需知道ostream的定义的。但当调用ostream的成员函数时就需要完整的定义了。

1.3用前置声明来代替e.h

前面代码我们看到,代码中使用了#include "e.h"来引用类E,但事实上,类E只是被用来声明参数类型和返回类型,因此不需要E的完整定义。并且在x.h文件中也不应该首先引入e.h头文件,我们只需用class E来代替#include.h

只需要前置声明时,绝对不要用#include包含相应的头文件。
通过上面3步修改,我们得到下面代码

//x.h:删除了不必要的头文件
//
#include<iosfwd>
#include<list>  //ABCD都不是模板,只有AC定义了虚函数
#include "a.h"
#include "b.h"
#include "c.h"
#include "d.h"
class E;  //现在用前置声明代替了原来的 #include "e.h"
class X:public A,private B
{
  public:
    X(const C&);
    B f(int,char*);
    C f(int,C);
    C& g(B);
    E h(E);
    virtual std::ostream& prrint(std::ostream& )const;
private:
    std::list<C> clist_;
    D d_;
};
inline std::ostream& operator<<(std::ostream& os,const X& x)
{
  return x.print(os);
}

1.4 Pimpl(句柄/本体惯用法)

  在上面代码中,我们看到保留a.h和b.h是必须的,不能删除这两个文件,这是因为X继承于类A和类B,因此在头文件中必须有基类的完整定义,这样编译器才能确定X对象的大小,虚函数以及其它基本信息。

  二个方面,我们也必须保留list c.h 和d.h文件,这是因为list<C>和D被用作X的私有数据成员,虽然C既不是基类也不是数据成员,但它被用来实例化数据成员list,主流编译器中都要求实例化list<C>时编译器能看到C的定义。

在这里,我们可容易地将类的私有部分封装起来,防止未授权的访问,但不幸的是在这里并没有将私有部分的依赖性封装。封装的要点在于:客户代码无需去了解或关心私有部分的实现细节。然而我们在类的头文件中还是可以看到类的私有部分,于是客户代码不得不依赖在这些私有部分中会使用到的所有类型。

我们的解决办法是设置一个Pimpl指针,指向一个进行了前置声明但没有定义的辅助类来隐藏类的私有成员,之前我们都是这样声明X类的:

//x.h
class X
{
  public:
    //公有成员和保护成员
  private:
    //私有成员,当私有成员发生变化时
    //所有客户代码都必须重新编译
};
利用Pimpl惯用法,我们写成这样

//x.h
class X
{
  public:
     //公有数据成员
  private:
    struct XImpl;  //前置声明类
    XImpl* pimpl_;//指向前置声明类的指针
};
//x.cpp
struct X::XImp;
{
  //私有成员完全被隐藏起来,当私有成员发生改变时
  //客户代码无需重新编译
};
在每个X对象中包含的XImpl对象都是动态分配的,如果将对象看做一个物理内存块,使用Pimpl的做法相当于从根本上削减对象中的大部分内存块,用另一个小得可怜的小内存块来代替,即这个不透明的指针Pimpl。

Pimpl惯用法的优点在于打破了编译期的依赖性。

在私有部分使用的类型定义,只有在类的实现中才会需要,而在客户代码中时不需要的,这样可削减额外的#include指令并提高编译速度

类的实现可以被修改,可自由增加或删减类的私有成员,而客户代码无需重新编译。

Pimpl惯用法的主要开销在于程序的执行性能

在每个构造/析构过程中都必须分配/释放内存

在每次访问被隐藏起来的成员时,至少需要一次额外的间接操作,存在多重间接操作。

对于被广泛使用的类,应该优先使用编译器防火墙惯用法(Pimpl惯用法)来隐藏实现细节,通过一个不透明的指针指向一个进行了前置声明但没有定义的类)来保存私有成员(包括状态变量和成员函数),在声明使用Pimpl惯用法时刻如下:

......
struct MyClassImpl;//前置声明
MyClass* pimpl_;
......

于是乎,现在代码简化如下:

#include<iosfwd>
#include "a.h"
#include "b.h"
class C;
class E;
class X:public A,private B
{
  ......
};

//file x.cpp中的实现
struct X::XImpl
{
  std::list<C> clist_;
  D d_;
};

1.5根据需要使用强的关系而不是更强

  继续分析代码我们发现,X在从A派生时使用公用继承,从B派生时使用私有继承。公用继承是对IS-A关系的建模,并满足Liskov替换原则。但这里B是私有继承,如果选择私有继承而不是聚合,那是为了获得对包含成员的访问。继承大多数情况下意味着要覆盖虚函数。然而这里B中没有声明虚函数,于是在这里也没必要选择继承这种更强的关系,如果X需要访问B中的包含成员函数或保护数据成员,那就得使用继承,在这里对类的继承是不必要的。于是有这样一条原则:如果使用聚合关系就已经足够,就不要使用继承

现在部分代码如下:

#include<iosfwd>
#include "a.h"  //类A的定义,因为存在继承虚函数覆盖所以必不可少
class B;
class C;
class E;
class X:public:A
 {
    ......
};





  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值