熟悉pimpl惯用法前,我们先来看看这样一段代码:
/***网络通信的基础类,SocketClient.h*/classCSocketClient{public:CSocketClient();~CSocketClient();private:CSocketClient(constCSocketClientrhs)=delete;CSocketClientoperator=(constCSocketClientrhs)=delete;public:voidSetProxyWnd(HWNDhProxyWnd);boolInit(CNetProxy*pNetProxy);boolUninit();intRegister(constchar*pszUser,constchar*pszPassword);voidGuestLogin();BOOLIsClosed();BOOLConnect(inttimeout=3);voidAddData(intcmd,conststd::stringstrBuffer);voidAddData(intcmd,constchar*pszBuff,intnBuffLen);voidClose();BOOLConnectServer(inttimeout=3);BOOLSendLoginMsg();BOOLRecvLoginMsg(intnRet);BOOLLogin(intnRet);private:voidLoadConfig();staticUINTCALLBACKSendDataThreadProc(LPVOIDlpParam);staticUINTCALLBACKRecvDataThreadProc(LPVOIDlpParam);boolSend();boolRecv();boolCheckReceivedData();voidSendHeartbeatPackage();private:SOCKETm_hSocket;shortm_nPort;charm_szServer[64];longm_nLastDataTime;//最近一次收发数据的时间longm_nHeartbeatInterval;//心跳包时间间隔,单位秒CRITICAL_SECTIONm_csLastDataTime;//保护m_nLastDataTime的互斥体HANDLEm_hSendDataThread;//发送数据线程HANDLEm_hRecvDataThread;//接收数据线程std::stringm_strSendBuf;std::stringm_strRecvBuf;HANDLEm_hExitEvent;boolm_bConnected;CRITICAL_SECTIONm_csSendBuf;HANDLEm_hSemaphoreSendBuf;HWNDm_hProxyWnd;CNetProxy*m_pNetProxy;intm_nReconnectTimeInterval;//重连时间间隔time_tm_nLastReconnectTime;//上次重连时刻CFlowStatistics*m_pFlowStatistics;};
CSocketClient类中的public方法作用于提供对外的接口以供第三方使用,而每个函数的具体实现都在SocketClient.cpp中,对于第三方使用者来说是透明的。这样的类一般作为公共库来提供给第三方使用。然而直接将这样的头文件提供给第三方使用,都会让提供者心里隐隐不安——因为公共类的成员变量(和私有函数)会暴露过多的实现细节,很容易就让使用者看出实现原理。这对于一些不想让使用者暴露关键性核心实现技术的应用中,这样的头文件是非常不好的。
那么有没有办法既能够不改变对外的接口,又能够不暴露那些关键性的实现?答案是有的。我们可以将代码稍微修改一下:
/***网络通信的基础类,SocketClient.h*/classImpl;classCSocketClient{public:CSocketClient();~CSocketClient();private:CSocketClient(constCSocketClientrhs)=delete;CSocketClientoperator=(constCSocketClientrhs)=delete;public://TODO:暂且保留这个接口,用于将来向UI层反馈当前网络状态voidSetProxyWnd(HWNDhProxyWnd);boolInit(CNetProxy*pNetProxy);boolUninit();intRegister(constchar*pszUser,constchar*pszPassword);voidGuestLogin();BOOLIsClosed();BOOLConnect(inttimeout=3);voidAddData(intcmd,conststd::stringstrBuffer);voidAddData(intcmd,constchar*pszBuff,intnBuffLen);voidClose();BOOLConnectServer(inttimeout=3);BOOLSendLoginMsg();BOOLRecvLoginMsg(intnRet);BOOLLogin(intnRet);//启动发送和接收数据工作线程//boolStartup();private:voidLoadConfig();staticUINTCALLBACKSendDataThreadProc(LPVOIDlpParam);staticUINTCALLBACKRecvDataThreadProc(LPVOIDlpParam);boolSend();boolRecv();boolCheckReceivedData();voidSendHeartbeatPackage();private:Impl*m_pImpl;};
上面代码中,如我们所见所有的关键性成员变量已经没有了,取代的是一个类型为Impl的指针变量m_pImpl。Impl类现在是完全对使用者透明,为了在当前类中可以使用Impl,使用了一个前置申明:
//原代码第5行classImpl;
然后我们就可以将刚才隐藏的成员变量放到这个类中去:
classImpl{public:Impl(){//TODO:你可以在这里对成员变量做一些初始化工作}~Impl(){//TODO:你可以在这里做一些清理工作}public:SOCKETm_hSocket;shortm_nPort;charm_szServer[64];longm_nLastDataTime;//最近一次收发数据的时间longm_nHeartbeatInterval;//心跳包时间间隔,单位秒CRITICAL_SECTIONm_csLastDataTime;//保护m_nLastDataTime的互斥体HANDLEm_hSendDataThread;//发送数据线程HANDLEm_hRecvDataThread;//接收数据线程std::stringm_strSendBuf;std::stringm_strRecvBuf;HANDLEm_hExitEvent;boolm_bConnected;CRITICAL_SECTIONm_csSendBuf;HANDLEm_hSemaphoreSendBuf;HWNDm_hProxyWnd;CNetProxy*m_pNetProxy;intm_nReconnectTimeInterval;//重连时间间隔time_tm_nLastReconnectTime;//上次重连时刻CFlowStatistics*m_pFlowStatistics;};
接下来我们在CSocketClient的构造函数中创建m_pImpl对象,在CSocketClient的析构函数中释放这个对象。
CSocketClient::CSocketClient(){m_pImpl=newImpl();}CSocketClient::~CSocketClient(){deletem_pImpl;}
这样,原来直接引用的成员变量,就可以在CSocketClient内部以m_pImpl-变量名的形式来引用了。
这里需要强调的是,在实际开发中,由于Impl类作为CSocketClient的辅助类,单独使用Impl类,没有存在的意义,所以我们一般会将Impl类定义为CSocketClient的内部类。即采用如下形式:
/***网络通信的基础类,SocketClient.h*/classCSocketClient{public:CSocketClient();~CSocketClient();//重复的代码省略...private:classImpl;Impl*m_pImpl;};
然后在ClientSocket.cpp中定义Impl类的实现:
/***网络通信的基础类,SocketClient.cpp*/classCSocketClient::Impl{public:SOCKETm_hSocket;shortm_nPort;charm_szServer[64];longm_nLastDataTime;//最近一次收发数据的时间longm_nHeartbeatInterval;//心跳包时间间隔,单位秒CRITICAL_SECTIONm_csLastDataTime;//保护m_nLastDataTime的互斥体HANDLEm_hSendDataThread;//发送数据线程HANDLEm_hRecvDataThread;//接收数据线程std::stringm_strSendBuf;std::stringm_strRecvBuf;HANDLEm_hExitEvent;boolm_bConnected;CRITICAL_SECTIONm_csSendBuf;HANDLEm_hSemaphoreSendBuf;HWNDm_hProxyWnd;CNetProxy*m_pNetProxy;intm_nReconnectTimeInterval;//重连时间间隔time_tm_nLastReconnectTime;//上次重连时刻CFlowStatistics*m_pFlowStatistics;}CSocketClient::CSocketClient(){m_pImpl=newImpl();}CSocketClient::~CSocketClient(){deletem_pImpl;}
在实际开发中,针对C++的使用,声明定义Impl类不仅可以使用class关键字,还可以使用struct关键字。不管是那种,public都是同时默认所有成员变量和方法的关键字,如果使用struct上述代码可以这么改写如下:
/***网络通信的基础类,SocketClient.h*/classCSocketClient{public:CSocketClient();~CSocketClient();//重复的代码省略...private:structImpl;Impl*m_pImpl;};
/***网络通信的基础类,SocketClient.cpp*/structCSocketClient::Impl{SOCKETm_hSocket;shortm_nPort;charm_szServer[64];longm_nLastDataTime;//最近一次收发数据的时间longm_nHeartbeatInterval;//心跳包时间间隔,单位秒CRITICAL_SECTIONm_csLastDataTime;//保护m_nLastDataTime的互斥体HANDLEm_hSendDataThread;//发送数据线程HANDLEm_hRecvDataThread;//接收数据线程std::stringm_strSendBuf;std::stringm_strRecvBuf;HANDLEm_hExitEvent;boolm_bConnected;CRITICAL_SECTIONm_csSendBuf;HANDLEm_hSemaphoreSendBuf;HWNDm_hProxyWnd;CNetProxy*m_pNetProxy;intm_nReconnectTimeInterval;//重连时间间隔time_tm_nLastReconnectTime;//上次重连时刻CFlowStatistics*m_pFlowStatistics;}CSocketClient::CSocketClient(){m_pImpl=newImpl();}CSocketClient::~CSocketClient(){deletem_pImpl;}
上述代码可以看出使用struct写法更简洁了。
按照上述的方式,CSocketClient这个类仅仅保留对外的接口,而其内部实现所使用的的变量和方法对使用者完全不可见了。我们称这种做法为pimpl惯用法,即PointertoImplementation(也有人认为是PrivateImplementation)。
pimpl惯用法具有以下优点:
保护核心数据和实现原理;
降低编译依赖,提高编译速度;
接口与实现分离。