C++ 在以下情况下使用goto可以吗?

C++ 在以下情况下使用goto可以吗?,c++,C++,好的,世纪问题:) 在你说或想任何事情之前,让我告诉你,我已经阅读了关于这个主题的两个类似问题,但我没有找到解决问题的明确方法。我的案例是具体的,我认为对于系统程序员来说是典型的 我经常遇到这种情况。我讨厌gotos,不知道为什么,可能是因为每个人都在大喊大叫这很糟糕。但直到现在,我还没有为我的特定场景找到更好的解决方案,我目前的做法可能比使用goto更丑陋 我的例子是:我使用C++(Visual C++)来开发Windows应用程序,而且我经常在程序中使用一组API。假设以下情况: int M

好的,世纪问题:)

在你说或想任何事情之前,让我告诉你,我已经阅读了关于这个主题的两个类似问题,但我没有找到解决问题的明确方法。我的案例是具体的,我认为对于系统程序员来说是典型的

我经常遇到这种情况。我讨厌gotos,不知道为什么,可能是因为每个人都在大喊大叫这很糟糕。但直到现在,我还没有为我的特定场景找到更好的解决方案,我目前的做法可能比使用goto更丑陋

我的例子是:我使用C++(Visual C++)来开发Windows应用程序,而且我经常在程序中使用一组API。假设以下情况:

int MyMemberFunction()
{
    // Some code... //

    if (!SomeApi())
    {
        // Cleanup code... //

        return -1;
    }

    // Some code... //

    if (!SomeOtherApi())
    {
        // Cleanup code... //

        return -2;
    }

    // Some more code... //

    if (!AnotherApi())
    {
        // Cleanup code... //

        return -3;
    }

    // More code here... //

    return 0; // Success
}
因此,在每个Api之后,我必须检查它是否成功,如果没有成功,则中止我的函数。为此,我使用了大量的
//清理代码//
,通常几乎重复,然后是
return
语句。该函数执行10个任务(例如使用10个API),如果任务6失败,我必须清理以前任务创建的资源。请注意,清理应该由函数本身完成,因此不能使用异常处理。而且,我看不出在这种情况下我能帮我多少忙

我想到的唯一方法是使用goto从所有此类失败案例跳转到一个清理标签,放在函数末尾

有没有更好的办法?在这种情况下,使用goto会被认为是不好的做法吗?那怎么办呢?这种情况对我来说非常典型(我相信对像我这样的系统程序员来说也是如此)

注:需要清理的资源有不同的类型。可能存在内存分配、需要关闭的各种系统对象句柄等

更新:

我认为人们仍然没有得到我想要的(可能我解释得不好)。我认为伪代码应该足够了,但下面是一个实际示例:

  • 我用CreateFile打开了两个文件。如果此步骤失败:我必须清理已打开的文件句柄(如果有)。稍后我将读取一个文件的一部分并写入另一个文件

  • 我使用SetFilePointer在第一个文件中定位读取指针。如果此步骤失败:我必须关闭上一步打开的句柄

  • 我使用GetFileSize获取目标文件大小。若api失败,或者文件大小异常,我必须进行清理:和前一步相同

  • 我分配指定大小的缓冲区来读取第一个文件。如果内存分配失败,我必须再次关闭文件句柄

  • 我必须使用ReadFile来读取第一个文件。如果失败,我必须:释放缓冲内存,关闭文件句柄

  • 我使用SetFilePointer在第二个文件中定位写指针。如果失败,则必须进行相同的清理

  • 我必须使用WriteFile来写入第二个文件。如果失败了,等等等等

  • 另外,假设我用critical section保护这个函数,在函数开头调用
    EnterCriticalSection
    之后,我必须在每个
    return
    语句之前调用
    LeaveCriticalSection


    现在请注意,这是一个非常简化的示例。可能会有更多的资源和更多的清理工作要做-大部分是相同的,但有时有点不同,这取决于哪一步失败了。但让我们在这个例子中讨论一下:我如何在这里使用RAII?

    不需要使用
    goto
    ,它容易出错,导致代码冗余且相当不安全

    使用,您不必使用
    goto
    。通过的RAII非常适合您的场景


    确保作用域中的所有资源都是RAII管理的(使用智能指针或自己的资源管理类),每当出现错误时,您所要做的就是返回,RAII将神奇地隐式释放您的资源。

    不需要使用
    goto
    ,这很容易出错,导致冗余和相当不安全的代码

    使用,您不必使用
    goto
    。通过的RAII非常适合您的场景


    确保范围内的所有资源都是RAII管理的(使用智能指针或自己的资源管理类),每当出现错误情况时,您所要做的就是返回,RAII将神奇地隐式释放您的资源。

    只要以前任务创建的资源是以类/对象的形式维护的,并且这些类/对象会在任务完成后进行清理,RAII就可以解决此问题。您提到了内存和系统对象句柄,所以让我们将它们作为起点

    // non RAII code:
    int MyMemberFunction() { 
        FILE *a = fopen("Something", "r");
    
        if (!task1()) {
           fclose(a);
           return -1;
        }
    
        char *x = new char[128];
    
        if (!task2()) {
            delete [] x;
            fclose(a);
            return -2;
        }
    }
    
    基于RAII的代码:

    int MyMemberFunction() { 
        std::ifstream a("Something");
        if (!task1())
            return -1; // a closed automatically when it goes out of scope
    
        std::vector<char> x(128);
    
        if (!task2())
            return -2; // a closed, x released when they go out of scope
        return 0; // again, a closed, x released when they go out of scope
    }
    

    编辑:虽然这是非常不寻常的,但是如果你真的需要使用C风格I/O,你仍然可以把它封装成一个C++类,类似于一个IoSo流传:

    class stream { 
        FILE *file;
    public:
        stream(char const &filename) : file(fopen(filename, "r")) {}
        ~stream() { fclose(file); }
    };
    
    这显然简化了(很多),但总体思路非常好。有一种不太明显,但通常更优越的方法:iostream实际上使用一个缓冲区类,使用
    下溢
    进行读取,使用
    上溢
    进行写入(同样,简化了,但这次没有这么多)。编写一个使用文件*来处理读/写操作的缓冲区类并不十分困难。所涉及的大多数代码实际上只不过是一个相当薄的转换层,用于为函数提供正确的名称,将参数重新排列为正确的类型和顺序,等等

    就记忆而言,你有两种不同的方法。一个是这样的,编写一个类似于向量的类,它完全充当您需要使用的任何内存管理的包装器(
    new
    /
    delete
    malloc
    /
    free
    ,等等)

    另一种方法是观察
    std::vector
    有一个分配器参数,因此它实际上已经只是一个包装器,您可以
    class stream { 
        FILE *file;
    public:
        stream(char const &filename) : file(fopen(filename, "r")) {}
        ~stream() { fclose(file); }
    };