C++必知必会
1、RAII
资源获取即初始化:指资源在我们拿到时就已经初始化,一旦不再需要该资源,就可以自动释放该资源。
#include <winsock2.h>
#include <stdio.h>
// 链接windows的socket网络库
#pragma comment(lib, "ws2_32.lib")
class ServerSocket
{
pubic:
ServerSocket()
{
m_bInit = false;
m_ListenSocket = -1;
}
~ServerSocket()
{
if (m_ListenSocket != -1) ::closesocket(m_ListenSocket);
if (m_bInit) ::WSACleanup();
}
bool DoInit()
{
// 初始化socket库
WORD wVersionRequested = MAKEWORD(2, 2);
WSADATA wsaData;
int err = ::WSAStartup(wVersionRequested, &wsaData);
if (err != 0) return false;
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) return false;
m_bInit = true;
// 创建用于监听的socket
m_ListenSocket = ::socket(AF_INET, SOCK_STREAM, 0);
if (m_ListenSocket == -1) return false;
return true;
}
bool DoBind(const char* ip, short port = 6000)
{
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr(ip);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(port);
if (::bind(m_ListenSocket, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)) == -1) return false;
return true;
}
bool DoListen(int backlog = 15)
{
if (::listen(m_ListenSocket, backlog) == -1) return false;
return true;
}
bool DoAccept()
{
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
char msg[] = "HelloWorld";
while (true)
{
SOCKET sockClient = ::accept(m_ListenSocket, (SOCKADDR*)&addrClient, &len);
if (sockClient == -1) break;
// 向客户端发送“HelloWorld”消息
::send(sockClient, msg, strlen(msg), 0);
::closesocket(sockClient);
}// end inner-while-loop
return false;
}
private:
bool m_bInit;
SOCKET m_ListenSocket;
}
int main(int argc, char** argv)
{
ServerSocket serverSocket;
if (!serverSocket.DoInit()) return false;
if (!serverSocket.DoBind("0.0.0.0", 6000)) return false;
if (!serverSocket.DoListen(15)) return false;
if (!serverSocket.DoAccept()) return false;
return 0;
}
以上代码并没有在构造函数中分配资源,而是单独使用一个DoInit
方法初始化资源,并在析构函数中回收相应的资源。这样在main
函数中就不用担心任何中间步骤失败而忘记释放资源了,因为一旦main函数调用结束,serverSocket
对象就会自动调用其析构函数回收相应的资源。这就是RAII惯用法的原理!
严格来说,以上代码中ServerSocket
的成员变量m_bInit
应该被设计成类静态成员,调用 WSAStartup
和 WSACleanup
的函数应该被设计成类的静态方法,因为它们只需在程序初始化和退出时各调用一次就可以了。
2、pimpl用法
有没有办法既能保持对外接口不变,又能尽量不暴露一些关键的成员变量和私有函数的实现方法呢
// Impl 前置声明
class Impl;
class CSocketClient
{
public:
CSocketClient();
~CSocketClient();
public:
void SetProxyWnd(HWND hProxyWnd);
bool Init(CNetProxy* pNetProxy);
bool Uninit();
int Register(const char* pszUser, const char* pszPassword);
void GuestLogin();
BOOL IsClosed();
BOOL Connet(int timeout = 3);
void AddData(int cmd, const std::string& strBuffer);
void AddData(int cmd, const char* pszBuff, int nBuffLen);
void Close();
BOOL ConnectServer(int timeout = 3);
BOOL SendLoginMsg();
BOOL RecvLoginMsg(int& nRet);
BOOL Login(int& nRet);
private:
Impl* m_pImpl;
}
在以上代码中,所有的关键成员变量都已经不存在了,取而代之的是一个类型为Impl
的指针成员变量m_pImpl
。
具体采用什么名称,读者完全可以根据自己的实际情况来定,不一定非要使用“Impl
”和“m_pImpl
”这样的名称。
Impl
类现在对使用者完全透明,为了在CSocketClient
类中引用 Impl 类,我们在SocketClient.h
文件中使用了一个前置声明,然后就可以将原来属于CSocketClient
类的成员变量转移到Impl
类中了:
class Impl
{
public:
Impl()
{
}
~Impl()
{
}
public:
SOCKET m_hSocket;
....
}
CSocketClient::CSocketClient()
{
m_pImpl = new Impl();
}
CSocketClient::~CSocketClient()
{
delete m_pImpl;
}
在实际开发中,由于Impl
类是CSocketClient
的辅助类,没有独立存在的必要,所以一般会将Impl
类定义成CSocketClient
的内部类。
C++11标准引入了智能指针对象,我们可以使用std::unique_ptr
对象来管理上述用于隐藏具体实现的m_pImpl
指针。
...
private:
struct Impl;
std::unique_ptr<Impl> m_pImpl;
...
修改构造函数和析构函数
CSocketClient::CSocketClient()
{
m_pImpl.reset(new Impl());
}
CSocketClient::~CSocketClient()
{
//delete m_pImpl;
}
C++14构造函数
CSocketClient::CSocketClient() : m_pImpl(std::make_unique<Impl>())
{
}