清理代码的正确方法是什么?

时间:2014-03-21 14:19:17

标签: c++ c windows

当你有多行代码启动其他对象时,是否有一种更清洁的方法来清理对象而不是下面显示的内容?我在一个函数中启动了几个对象,并检查它们是否失败 - 但我有一堆冗余代码,我必须继续输入。下面显示的是正确的方法吗?还是有更干净的方式?我知道do {} while(false)方法和goto方法 - 但它们不干净并且感觉很乱。

    if( bind(s, (sockaddr *)&saddr, sizeof(sockaddr)) == SOCKET_ERROR ) {
        printf("bind() failed.\n");
        closesocket(s);
        CloseHandle(g_hIOCompletionPort);
        CloseHandle(g_hShutdownEvent);
        WSACleanup();
    }

    if( listen(s, 60) == SOCKET_ERROR ) {
        printf("listen() failed.\n");
        closesocket(s);
        CloseHandle(g_hIOCompletionPort);
        CloseHandle(g_hShutdownEvent);
        WSACleanup();
    }

    g_hAcceptEvent = WSACreateEvent();
    if( g_hAcceptEvent == WSA_INVALID_EVENT ) {
        printf("WSACreateEvent() failed.\n");
        closesocket(s);
        CloseHandle(g_hIOCompletionPort);
        CloseHandle(g_hShutdownEvent);
        WSACleanup();
    }

3 个答案:

答案 0 :(得分:3)

如果您正在使用C ++,那么RAII (Resource Acquisition Is Initialization)是首选方式。基本上,您获取或附加资源(通常在施工期间)并在销毁期间将其释放。

例如,在您的代码中,您拥有某种Handle类,该类保留在g_hIOCompletionPort句柄中,并在其析构函数中调用CloseHandle

答案 1 :(得分:3)

大多数情况下,RAII将在C ++中使用,其中代码使用具有构造函数和析构函数的对象进行组织,并且在析构函数中执行清理。因此,破坏对象就足够了,这限制了复制代码的数量。

class Server {
    SOCKET s;
    HANDLE iocp;
    HANDLE shutdown;
    std::string err_str;
public:
    ~Server () {
        if (!err_str.epmty()) std::cerr << err_str << '\n';
        closesocket(s);
        CloseHandle(iocp);
        CloseHandle(shutdown);
        WSACleanup();
    }
    //...
};

除了RAII之外,C ++还提供了异常处理,也可以在您的情况下使用。 try块将具有套接字代码。当套接字代码在出错时抛出异常,catch块可以处理清理。

try {
    if (bind(...) == SOCKET_ERROR) {
        throw ...something...;
    }
    if (listen(...) == SOCKET_ERROR) {
        throw ...something...;
    }
    ...
}
catch (...something...) {
    ...cleanup code;
}

在C中,没有RAII等价物。也没有例外。但是,可以使用setjmp()longjmp()模拟异常处理,就像使用cexcept一样。虽然没有直接支持RAII,但没有什么可以阻止您将C代码组织成具有相关清理功能的对象。

struct Server {
    SOCKET s;
    HANDLE iocp;
    HANDLE shutdown;
    const char *err_str;
};

void destroy_server (Server *server) {
    /* ... */
}

如果序列后面有很多代码,但是其中任何代码都需要进行清理,那么可以像时尚一样在状态机中组织代码。

enum { STATE_INIT, STATE_SUCCESS, STATE_ERROR, STATE_STOP } state = STATE_INIT;

while (state != STATE_STOP) {
    switch (state) {
    case STATE_INIT:    state = do_server_init(); break;
    case STATE_SUCCESS: state = do_server(); break;
    case STATE_ERROR:   state = do_server_cleanup(); break;
    case STATE_STOP:    break;
    default:            fprintf(stderr, "unexpected state: %d\n", state);
                        state = STATE_ERROR;
    }
}

答案 2 :(得分:0)

在C ++中RAII是这种情况的一种非常常见的模式。

对于普通C:如果我写goto,我将被杀死,所以我没有。但请在此处查看示例:Valid use of goto for error management in C?