一 概述:
在实际来发过程中,经验略少的开发者经常遇到这样的问题:只对一个头文件中的定义进行了简单的修改,却导致很多文件的重新编译,浪费了大量的时间。这种使人厌烦的问题是由于那些文件依赖了(#include)你修改的头文件,当然,依赖是迟早的事,但是有什么好的解决办法减少没必要的编译动作呢?下面进行讲述。
最根本的出发点是尽量避免在.h文件中包含(#include)其他的.h文件,这样在对某一.h文件进行修改后,不会对另一个.h文件产生影响,从而避免多余的编译动作。比较抽象,举例说明:比如有两个.h文件,a.h和.h,还有一个.cpp文件c.pp,其依赖于b.h文件。如果b.h文件依赖于a.h文件,在对a.h进行修改后会导致b.h的#include内容发生变化,尽管其内容并未修改,但在编译过程中会使得c.cpp重新编译。
二 实现:
下面讲述几种常用的解决方法:
1 使用类的前向声明:
基本思路是利用类的指针取代类的对象。因为不管何种类型,指针所占内存是固定大小的(不同平台下有所不同),基本形式如下://*******a.h
class CAddress
{
public:
string m_strAddr;
};
//******b.h
#include “a.h”
class CStudent
{
private:
string m_strName;
char m_age;
CAddress m_cAddress;
};
class CShool
{
private:
string m_strName;
};
//******c.cpp
#include “b.h”
int main()
{
CShool shool;
return 0;
}
代码如上所示,此时对a.h进行修改:
//*******a.h
class CAddress
{
public:
string m_strAddr;
int m_numDoor;
};
尽管此时并未使用a.h中的类,再次编译时,也会导致c.cpp再次编译,而其与a.h无之间联系。问题就出在b.h中有 “#include a.h”。
下面我们通过”前向声明“对b.h进行修改,如下:
//******b.h
//! 前向声明
class CAddress;
class CStudent
{
private:
string m_strName;
char m_age;
CAddress *m_pAddress;
};
class CShool
{
private:
string m_strName;
};
再修改a.h中CAddress的定义,不会导致c.cpp再次编译。
如果有.cpp文件使用CStudent类,只需#include “a.h”即可。如下:
//*****d.cpp
#include “b.h”
#include “a.h”
void Test()
{
CStudent student;
//...
}
当然,针对a.h的修改,d.cpp的重新编译是不可避免的。
2.使用PImpl技术:
pImpl,Private Implementation(or Pointer to Implementation)的缩写。是一种在类中只定义接口,而将私有数据成员封装在另一个实现类中的惯用法。该方法主要是为了隐藏类的数据以及减轻编译时的压力。在C++中,此种方法成为“柴郡猫”技术(其字面意思自行查阅)。它使用指向实现的指针来隐藏实现细节。即在主类定义中定义接口,私有成员封装在一个实现类中,主类使用一个形如”struct XxxxImpl* pimpl_”的不透明指针存储私有成员,解开类接口与实现的耦合,真正实现了接口和实现的分离。例如:
//*****test.h
//!PImpl前向声明
class CTestImp;
class CTest
{
public:
CTest();
~CTest();
void Public_Method();
private:
//! PImpl指针
CTestImp *pimpl_;
};
//*****test.cpp
class CTestImp
{
public:
void Private_Method() {}
int private_var_;
};
CTest::CTest() : pimpl_( new CTestImp() )
{
}
CTest::~CTest()
{
if(pimpl_)
{
delete pimpl_;
}
}
void CTest::Public_Method()
{
pimpl_->Private_Method();
pimpl_->private_var_ = 0;
}
为了更好的封装性,将XxxxImpl类定义成一个local类,修改如下:
//*****test.h
class CTest
{
public:
CTest();
~CTest();
void Public_Method();
private:
//!PImpl前向声明
class CTestImp;
//! PImpl指针
CTestImp *pimpl_;
};
//*****test.cpp
class CTest::CTestImp
{
public:
void Private_Method() {}
int private_var_;
};
CTest::CTest() : pimpl_( new CTestImp() )
{
}
CTest::~CTest()
{
if(pimpl_)
{
delete pimpl_;
}
}
void CTest::Public_Method()
{
pimpl_->Private_Method();
pimpl_->private_var_ = 0;
}
除了上述两种方法外,还可以使用基于接口的编程等方法,即提供接口供外部调用,具体实现在其子类中实现。这里用到的技术就是虚函数的多态性,具体细节在此不展开了。