码迷,mamicode.com
首页 > 其他好文 > 详细

减少编译时源文件之间的依赖

时间:2015-05-09 11:49:45      阅读:165      评论:0      收藏:0      [点我收藏+]

标签:c++   文件依赖   编译   面向对象   编译器   

描述

     在大型项目开发中,往往编译时间非常长,我见过需要编译15分钟的项目,这对于开发人员来说无疑是无奈的等待。如果每次一个小的代码修改,整个项目都要重新编译的话,时间成本是非常高,为了说明这个问题,下面举一个例子:

 如下类:

A.hpp

class A
{
public:
    void foo();
 
private:
    AMember m_member;
}

      如果该头文件被包含在预编译头文件中,若修改类A,每个.cpp源文件都需要重新编译,因为A.h被预编译头文件包含,所有.cpp可能都需要类A。当我们修改类AMember,所有的文件也需要重新编译。当然上面的结果是我们不想要的,能不能只让包含AMember的源文件重新编译?下面将介绍如何让编译器只编译修改的部分。

       类的定义一般包含类的实现细节,如成员变量。上面的例子中包含成员变量AMember,因此A.hpp需要包含头文件AMember.hpp,如#include “AMember.hpp”,这无疑增加了编译的依赖,所以需要移除上面的编译依赖。为了减少这种依赖,下面介绍几种方法。

方法一:利用Handle类

该方法在Scott Meyers写的书籍《EffectiveC++》有描述,一个Hanldle类是包含一个具体实现类的对象,如下:

A.hpp

class AImpl;
class A
{
public:
    void foo();

private:
    AImpl * impl;//指向Handle类的指针
};

AImpl.hpp

#include “AMember.hpp”
class AImpl
{
public:
    void foo();

private:
    AMember m_member;
};

A.cpp

#include "AImpl.hpp"
#include “A.hpp”
void A::foo()
{
    impl->foo();
}

在头文件A.hpp中采用类前置声明AImpl,没有包含该类的头文件AImpl.hpp,主要是在头文件A.hpp中,只使用指向AImpl的指针,但在A.cpp中需要包含AImpl.hpp。该方法的缺点如下:

1.每个A对象需要一个额外的指针

2.需要在运行是重链成员函数

3.需要动态为AImpl分配内存。

方法二:利用Protocol类

Protocol类是一个抽象类,只代表具体类的一个接口。如下:

B.hpp

class B
{
public:
    virtual void foo() = 0;
    //由于该类为抽象类,不能直接实例化对象,需要提供一个方法实例化
    static B * makeB();
};

BImpl.hpp

#include "B.hpp"
#include “AMember.hpp”
class BImpl : public B
{
public:
    void foo();

private:
    AMember m_member;
};

B.cpp

#include  "BImpl.hpp"
#include "B.hpp"
B * B::makeB()
{
    return new BImpl;
}

从上面可以看到,源文件B.cpp只需要包含B.hpp和BImpl.hpp。该方法利用一个抽象类,并且提供一个实例化的接口来构造子类BImpl

该方法的缺点如下:

1.需要一个辅助函数来构造一个对象

2.需要手工释放由辅助函数构造的对象。

3.使用了虚函数,需要提前加入虚表。

4.运行时链接虚函数。

方法三:利用模板

D.hpp

template <class T>
class TD
{
public:
    void foo();
};

// 前置声明一个类
class DImpl;

typedef TD<DImpl> D;

DImpl.hpp

#include "D.hpp"

class DImpl : public TD<DImpl>
{
public:
    void foo();

private
    AMember m_member;
};

D.cpp

#include "DImpl.hpp"

void TD<DImpl>::foo()
{
    (static_cast<DImpl *>(this))->foo();
}
void DImpl::foo()
{
	m_member.foo();
}

上面的TD的this指针指向DImpl.有了该指针,就可以访问该类的函数。

方法三与上面两种方法的对比:

与方法一对比:

     a.不需要额外的变量 

     b.在编译期间链接函数,不需要在运行期间。

     c.不需要手工申请和释放内存

与方法二对比:

      a.不需要辅助类进行对象实例化,可以通过TD<DImpl>.进行实例化。

      b.不需要手工释放对象,本方法对象管理和根据自身的初始化方式决定。

      c.没有虚表   d.没有虚函数,在编译期间进行链接。

      技术分享注意:基于模板的方法三有一个Bug,当类DImpl包含数据成员时,本方法会失效。AMember的构造函数不会被调用,同时DImpl的构造函数也不会调用,丢失了C++的特性。如果一个类包含数据成员,不能使用方法三。

方法四:利用静态变量

E.hpp

//编译器不需要知道AMember的具体实现
class AMember;

class E
{
public:
    const AMember& GetMember();
};

E.CPP

#include "E.hpp"
#include "AMember.hpp"
const AMember& E::GetMember()
{
    static AMember member;

    return member;
}
void E::foo()
{
   GetMember().foo
}

     如果需要使用类AMember,只需要包含E.hpp,然后调用E::GetMember,本方法可以促使自己采用面向对象进行编程,可以减少依赖。由于使用了静态变量是、,类E只有一个实例化对象AMember,有点类似单例模式。

       总结上面的方法就是在编译时间和运行时间进行衡量,有的利用运行时间换编译时间,如动态分配内存和释放内存都会损耗运行时间,对于这类运行时间优化可以采用内存池技术。同时虚表也会降低运行速度。本文着重讲解减少编译时源文件之间的依赖。技术分享

减少编译时源文件之间的依赖

标签:c++   文件依赖   编译   面向对象   编译器   

原文地址:http://blog.csdn.net/xiaoding133/article/details/45599181

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!