【发布时间】:2021-02-18 19:56:42
【问题描述】:
我通常不会显式调用析构函数。但我正在设计 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 =====
好的,总结一下答案,我得出了这个结论:
-
显式调用析构函数通常不是一个好主意,即使它按预期工作,这是不寻常的,其他将处理您的代码的 C++ 程序员可能会对这种方法感到困惑。所以最好避免显式调用析构函数。
-
将我原来的 RAII 类分解为微型 RAII 类看起来是一个不错的解决方案。但我担心我的真实代码中有太多 API 调用需要清理(closesocket、CloseHandle、DeleteCriticalSection 等)。其中一些只被调用一次并且从未被重用,并且将它们全部移动到单独的 RAII 类对我来说似乎太狂热了。这也会增加我的代码。
-
在我看来,最有帮助的答案来自 MM:
更好的解决方案是将初始化代码保留在 构造函数,并在抛出前调用清理函数。
按照 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 类。
【问题讨论】:
-
调用析构函数有特殊意义,它结束了objext的生命周期。当构造函数抛出时,生命周期从未开始。你不能从构造函数中调用析构函数。
-
其实我测试了这个把MessageBox放到析构函数里面,当构造函数抛出异常的时候就出现了。
-
你在这里玩的是未定义的行为。未定义行为的阴险之处在于,它看起来像是在工作,即使实际上并没有。
-
显式调用析构函数根本是不好的做法。在这种情况下,这是完全没有必要的。
-
"其中一些只被调用一次并且从不重复使用,并且将它们全部移动到单独的 RAII 类中对我来说似乎太狂热了。这也会增加我的代码。"这是完全不正确的,如果处理得当,它会让你的代码更短更干净。
标签: c++ class exception constructor destructor