在C ++中从构造函数中显式地调用析构函数是不好的做法吗?

台伯河

我通常不明确地调用析构函数。但是我正在设计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类实例的静态方法吗?

=====更新=====

好,总结一下答案,我得出以下结论:

  • 明确地调用析构函数通常不是一个好主意,即使它按预期工作也是如此,这是不寻常的,并且其他将要处理您的代码的C ++程序员可能会对此方法感到困惑。因此,最好避免显式调用析构函数。

  • 将我原来的RAII类分解为微型RAII类似乎是一个不错的解决方案。但是,恐怕我的真实代码中有太多需要进行清理的API调用(closesocket,CloseHandle,DeleteCriticalSection等)。其中一些仅被调用一次,并且永远不会被重用,将它们全部移到单独的RAII类中对我来说似乎太疯狂了。这也会增加我的代码。

  • 我认为最有帮助的答案是来自MM

更好的解决方案是将初始化代码保留在构造函数中,并在丢弃之前调用cleanup函数。

遵循MM的建议,我以这种方式重写了代码:

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类。

贾罗德42

建议的设计方式是什么?

我会说:更多的RAII。就像是:

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;
};

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

根据C ++标准,显式调用构造函数和析构函数是否安全?

C ++:显式调用模板参数的typedef的析构函数

析构函数会自动在C ++中调用delete []吗?

C ++中的“隐式析构函数”

C ++中的析构函数直接调用

析构函数在C ++中调用向量

C ++中的析构函数和构造函数排序

C++ 你能在构造函数或析构函数中引用 this 指针吗?

C#中的显式构造函数调用

C++11:我可以显式调用基类析构函数来销毁派生类吗?

C ++。在析构函数中调用虚拟成员函数

C ++为什么在堆栈中构造完对象后立即调用析构函数?

C ++析构函数-继承上下文中的显式调用

在多级继承中调用析构函数(C ++)

在C ++中调用虚拟析构函数的顺序

为什么在构造时调用C ++类的析构函数?

C ++中的纯虚拟析构函数

C ++析构函数中的堆栈溢出

析构函数中的c ++异常

C ++中的循环链表的析构函数?

C ++在析构函数中杀死线程

C ++构造函数/析构函数

如何区分C ++中的隐式/显式构造函数调用?

C ++构造函数/析构函数调用&在动态创建的数据上调用“ new”会删除旧数据吗?

什么时候在C ++中确切地调用了一个类的析构函数?

C++ 中的自定义析构函数 x 默认构造函数

C ++,静态对象构造函数中的异常会绕过先前静态对象的析构函数

为什么C ++和Java中的构造函数调用需要显式类型参数?

在C ++中从派生类的析构函数调用虚函数