在C++项目中常用的设计机制——Pimple是什么?刚开始看到这个机制的时候也是一脸的茫然。博主是在看源码的过程中第一次接触到Pimpl的使用。于是就在网上进行一番搜索。这里主要是记录自己对Pimpl机制的学习。
1.Pimpl机制的作用
PImpl(private implementation) 其主要作用是解开类的使用接口和实现的耦合。
关于耦合性:耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的
耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个准则就是高内聚低耦合。
——引用百度百科的解释
2.Pimpl机制的实现思想
PImpl(private implementation)的思想是将私有数据和函数放入一个单独的类中,并保存在一个实现文件中,然后在头文件中对这个类进行前向声明并保存一个指向该实现类的指针。类的构造函数分配这个pimpl类,而析构函数则释放它。这样可以解开类的使用接口和实现的耦合。
在这里我们谈谈开发过程中我们可能遇到的问题及为什么使用Pimpl。
在实际的项目中,我们可能或多或少的遇到这样的情况:
有时候需要修改某个类的实现,而这样的修改会导致所有包含改类的代码需要重新编译,当这个类被项目中的大量使用时,那么编译时间是很长的;别人使用“不合法的方法”调用了我们编写的接口,突破了我们期望给予的限制。
使用Pimpl能给我们带来的好处:
1.将具体类的实现封装到另一个类里面,使用者只能看到一个向前的声明和对应的指针。除非使用者去修改对应的实现,否则,他将无法知道具体的实现,也就无法通过一些非法的方式去访问。从一定程度上防止了封装的泄漏;
2.同时类与数据成员之间的耦合最低,类看起来总是一个样子,包含该类声明的文件也不会因为类实现的改变而从新编译,节约编译时间。
3.未使用Pimpl的设计思想
假设我们需要实现某个消息类,这个消息类提供打印消息的方法。
#ifndef __MESSAGE_H__
#define __MESSAGE_H__
#include
class CMessage
{
public:
CMessage(std::string &strWarn);
~CMessage();
void printf();
private:
std::string m_strWarnMsg;
};
#endif //#ifndef __MESSAGE_H__
看上去虽然使用者只需要知道printf()接口就行,但是当我们提供该头文件的时候,其实已经暴露了私有的实现。
再来,如果我们需要在这个消息类中添加一些附加信息。
#ifndef __MESSAGE_H__
#define __MESSAGE_H__
#include
class CMessage
{
public:
CMessage(std::string &strWarn);
~CMessage();
void printf();
private:
std::string m_strWarnMsg;
std::string m_strAdditionMsg;
};
#endif //#ifndef __MESSAGE_H__
虽然使用printf()接口仍然可以得到想要的结果,但是使用者不得不编译所有包含该头文件的代码。
4.使用Pimpl的设计思想
使用上面提到的PImpl(private implementation)的思想。我们从新设计该类的实现。
我们CMessage类实现如下所示:
#ifndef __MESSAGE_H__
#define __MESSAGE_H__
#include
#include
class CPimpl;
class CMessage
{
public:
CMessage(std::string &strWarn);
~CMessage();
void printf();
private:
boost::scoped_ptr spPimpl;
};
#endif //#ifndef __MESSAGE_H__
#include "Message.h"
#include "Pimpl.h"
CMessage::CMessage(std::string &strWarn)
:spPimpl(new CPimpl(strWarn))
{
}
CMessage::~CMessage()
{
}
void CMessage::printf()
{
spPimpl->printf();
}
这里我们使用了boost库的智能指针,避免了手动释放。类CMessage提供了同样的方法,但是我们将真正的实现封装到类CPimpl中去。类CMessage的使用者对此一无所知。
我们真正的实现如下所示:
#ifndef __PIMPL_H__
#define __PIMPL_H__
#include
class CPimpl
{
public:
CPimpl(std::string &strWarnMsg);
~CPimpl();
void printf();
private:
std::string m_strWarnMsg;
std::string m_strAdditionMsg;
};
#endif //#ifndef __PIMPL_H__
#include
#include "Pimpl.h"
CPimpl::CPimpl(std::string &strWarnMsg)
:m_strWarnMsg(strWarnMsg)
, m_strAdditionMsg(__FILE__)
{
}
CPimpl::~CPimpl()
{
}
void CPimpl::printf()
{
std::cout << m_strWarnMsg << std::endl;
std::cout << m_strAdditionMsg << std::endl;
}
当我们需要在类CPimpl添加成员变量以便满足某项需求时,我们不需要编译所有包含Message.h的代码,只需要编译Pimpl.cpp。极大的降低了编译的依赖。
当然,使用Pimpl也有它的缺点:比如我们需要为我们创建的每个分配和释放,当然我们可以使用智能指针;并且使用了指针,增加了间接性;这就需要我们去权衡。