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

前端之家收集整理的这篇文章主要介绍了减少编译时源文件之间的依赖前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

描述

在大型项目开发中,往往编译时间非常长,我见过需要编译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,有点类似单例模式。

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

原文链接:https://www.f2er.com/javaschema/284783.html

猜你在找的设计模式相关文章