首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在C++中,从构造函数中显式调用析构函数是不是不好的做法?

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

Stack Overflow用户
提问于 2020-11-06 03:49:07
回答 6查看 235关注 0票数 3

我通常不明确地调用析构函数。但是我正在设计TCP服务器类,它看起来如下所示:

代码语言:javascript
运行
复制
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调用需要清理(密闭、CloseHandle、DeleteCriticalSection等等)。其中一些只被调用过一次,从来没有被重用过,而将它们全部转移到单独的RAII类对我来说似乎太狂热了。这也会增加我的代码.

在我看来,M.M最有帮助的答案是

一个更好的解决方案是将初始化代码保留在构造函数中,并在抛出之前调用清理函数。

按照M.M.的建议,我以这样的方式重写了代码:

代码语言:javascript
运行
复制
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类。

EN

回答 6

Stack Overflow用户

回答已采纳

发布于 2020-11-06 08:48:59

推荐的设计方法是什么?

我想说的是:更多的瑞伊。类似于:

代码语言:javascript
运行
复制
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
};

最后

代码语言:javascript
运行
复制
class Server {
public:
    Server() : wsa(), socket(..) {}

private:
    WSARaii wsa;
    Socket socket;
};
票数 3
EN

Stack Overflow用户

发布于 2020-11-06 04:13:33

是否从构造函数中显式调用析构函数在C++中的不良实践?

是。如果您调用尚未构造的对象的析构函数,则程序的行为是未定义的。

行为不明确是件坏事。应尽可能避免这种情况。

推荐的设计方法是什么?

遵循单一责任原则(SRP),资源获取是初始化(RAII)模式。

特别是,您的Server有太多的责任。您应该创建一个单独的类来管理套接字。在该类的构造函数中,调用scoket,在析构函数中调用该类,调用closesocket。保持包含的套接字始终有效(可关闭)或INVALID_SOCKET的类不变,如果有效且永不泄漏,则始终是唯一的(即,如果不先关闭,则不会覆盖该值)。这是RAII模式。

为wsa数据创建类似的包装器。

Server中,存储这些包装器类型的成员。这样,Server就不需要自定义析构函数或其他特殊的成员函数了,因为这些函数是由管理自己的成员处理的。

票数 5
EN

Stack Overflow用户

发布于 2020-11-06 04:07:47

析构函数只应由完全构造的对象调用。

您可以创建一个Init()和CleanUp()函数,而不是将安装代码放在构造函数中。这也将使您的服务器对象的构造速度更快。

代码语言:javascript
运行
复制
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;
};

呼叫方代码:

代码语言:javascript
运行
复制
Server server;
if (!server.init()) {
   server.CleanUp();
}
票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/64708587

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档