我通常不明确地调用析构函数。但是我正在设计TCP服务器类,它看起来如下所示:
class Server {
public:
Server() {
try {
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData))
throw std::runtime_error("WSAStartup function failed.");
...
if ((m_scListener = socket(pAddr->ai_family, pAddr->ai_socktype, pAddr->ai_protocol)) == INVALID_SOCKET)
throw std::runtime_error("'socket' function failed.");
...
}
catch (std::exception& ex) {
this->~Server();
throw;
}
}
~Server() {
if (m_scListener != INVALID_SOCKET) {
closesocket(m_scListener);
m_scListener = INVALID_SOCKET;
}
WSACleanup();
}
private:
SOCKET m_scListener = INVALID_SOCKET;
}
上面的代码是否被认为是错误的实践或设计?推荐的设计方法是什么?我是这样写的,因为构造函数不能返回NULL。我是否应该将构造函数设置为私有,并编写创建Server类实例的静态方法?
===== U P D A T E =====
好吧,总结一下答案,我得出了这样的结论:
在我看来,M.M最有帮助的答案是
一个更好的解决方案是将初始化代码保留在构造函数中,并在抛出之前调用清理函数。
按照M.M.的建议,我以这样的方式重写了代码:
class Server {
public:
Server() {
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData))
ThrowError("WSAStartup function failed.", true);
...
if ((m_scListener = socket(pAddr->ai_family, pAddr->ai_socktype, pAddr->ai_protocol)) == INVALID_SOCKET)
ThrowError("'socket' function failed.", true);
...
}
~Server() { CleanUp(); }
private:
SOCKET m_scListener = INVALID_SOCKET;
void ThrowError(const char* error, bool cleanUp) {
if (cleanUp)
CleanUp();
throw std::runtime_error(error);
}
void CleanUp() {
if (m_scListener != INVALID_SOCKET) {
closesocket(m_scListener);
m_scListener = INVALID_SOCKET;
}
WSACleanup();
}
};
我相信这个设计遵循RAII模式,但是只有一个类而不是3-4个微RAII类。
发布于 2020-11-06 08:48:59
推荐的设计方法是什么?
我想说的是:更多的瑞伊。类似于:
class WSARaii
{
public:
WSARaii()
{
if (WSAStartup(MAKEWORD(2, 2), &wsaData))
throw std::runtime_error("WSAStartup function failed.");
}
~WSARaii()
{
WSACleanup();
}
WSARaii(const WSARaii&) = delete;
WSARaii& operator =(const WSARaii&) = delete;
private:
WSADATA wsaData;
};
class Socket
{
public:
Socket(..) : m_scListener(socket(pAddr->ai_family, pAddr->ai_socktype, pAddr->ai_protocol) {
if (m_scListener == INVALID_SOCKET)
throw std::runtime_error("'socket' function failed.");
}
~Server() {
if (m_scListener != INVALID_SOCKET) {
closesocket(m_scListener);
}
}
private:
SOCKET m_scListener
};
最后
class Server {
public:
Server() : wsa(), socket(..) {}
private:
WSARaii wsa;
Socket socket;
};
发布于 2020-11-06 04:13:33
是否从构造函数中显式调用析构函数在C++中的不良实践?
是。如果您调用尚未构造的对象的析构函数,则程序的行为是未定义的。
行为不明确是件坏事。应尽可能避免这种情况。
推荐的设计方法是什么?
遵循单一责任原则(SRP),资源获取是初始化(RAII)模式。
特别是,您的Server
有太多的责任。您应该创建一个单独的类来管理套接字。在该类的构造函数中,调用scoket
,在析构函数中调用该类,调用closesocket
。保持包含的套接字始终有效(可关闭)或INVALID_SOCKET
的类不变,如果有效且永不泄漏,则始终是唯一的(即,如果不先关闭,则不会覆盖该值)。这是RAII模式。
为wsa数据创建类似的包装器。
在Server
中,存储这些包装器类型的成员。这样,Server
就不需要自定义析构函数或其他特殊的成员函数了,因为这些函数是由管理自己的成员处理的。
发布于 2020-11-06 04:07:47
析构函数只应由完全构造的对象调用。
您可以创建一个Init()和CleanUp()函数,而不是将安装代码放在构造函数中。这也将使您的服务器对象的构造速度更快。
class Server {
public:
Server() = default;
bool Init() {
try {
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData))
throw std::runtime_error("WSAStartup function failed.");
...
if ((m_scListener = socket(pAddr->ai_family, pAddr->ai_socktype, pAddr->ai_protocol)) == INVALID_SOCKET)
throw std::runtime_error("'socket' function failed.");
...
return true;
}
catch (std::exception& ex) {
return false;
}
}
void CleanUp() {
if (m_scListener != INVALID_SOCKET) {
closesocket(m_scListener);
m_scListener = INVALID_SOCKET;
}
WSACleanup();
}
~Server() {
CleanUp();
}
private:
SOCKET m_scListener = INVALID_SOCKET;
};
呼叫方代码:
Server server;
if (!server.init()) {
server.CleanUp();
}
https://stackoverflow.com/questions/64708587
复制相似问题