如何:异常和非异常代码之间的接口

本文介绍如何实现一致异常处理在 c. c++ 模块,以及如何向/从错误代码将这些异常在异常边界。

有时 c. c++ 模块必须与不使用异常的代码的接口 (非异常代码)。 此类接口称为" 异常边界。 例如,您可能要对您的 C++ 程序的 Win32 函数 CreateFileCreateFile 不会引发异常;而设置可以由 GetLastError 函数检索的错误代码。 如果您的 C++ 程序很重要,则在您可能希望让一致的基于异常的错误处理的策略。 正因为您与非异常代码的接口和都不希望组合在 C++ 模块,的基于异常及基于非异常的错误策略您可能不希望放弃异常。

从 C++ 调用非异常功能

当您调用从 C++ 时的非异常功能,则该目的是包装在检测所有错误。然后引发异常的 c. c++ 函数的函数。 当设计这样的包装函数时,首先确定异常确保哪种类型提供的:NO-引发,强烈或基。 接下来,设计功能,以便,正确释放所有资源,例如,文件句柄,如果引发了异常。 通常,这意味着您使用智能指针或类似的资源管理器来自己资源。 有关设计注意事项的更多信息,请参见 如何:异常安全模型

Hh279691.collapse_all(zh-cn,VS.110).gif示例

下面的示例演示使用的 C++ 函数 Win32 CreateFileReadFile 在内部函数打开并读取两个文件。 File 选件类是"获取资源即初始化" (raii) 文件句柄的 (RAII) 包装。 其构造函数检测一个“文件未找到”条件并引发异常传播 C++ 模块的调用堆栈的错误。 如果引发异常,在 File 对象完全构造后,析构函数自动调用 CloseHandle 释放文件句柄。 (如果您愿意,可以为这个目的使用活动模板库 (ATL) CHandle 选件类或 unique_ptr 使用自定义 deleter。)DiffHandles 功能检测读取错误然后引发 C++ 异常。 DiffFiles 函数不引发也不会捕捉任何异常,则,仍出现异常。 它允许所有异常传播调用堆栈。 所有功能提供了一个强大的异常保证;如果在这些功能在任何时候引发,资源不会泄漏,但不修改程序状态。

#include <Windows.h>
#include <iostream>
#include <string>
#include <stdexcept>

using namespace std;


class Win32Exception : public runtime_error
{    
    DWORD err;
    static const int BUF_SIZE = 1024;
    string msg;
    string localMsg;

public:

    Win32Exception(DWORD error, string msg): runtime_error(string("Win32Exception")), err(error), localMsg(msg) {}

    // Generic message output function.
    const char* what()
    {
        char buf[BUF_SIZE];
        FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, 0, err, 0, (LPSTR) &buf, BUF_SIZE - 1, 0); 
        msg = string(buf) + ":" + localMsg;
        return msg.c_str();       
    }


    const DWORD GetErrorCode() {return err;}
};

void ThrowLastErrorIf(bool expression, string msg) 
{ 
    if (expression) 
    { 
        throw Win32Exception(GetLastError(), msg); 
    } 
} 

bool DiffHandles(HANDLE file1, HANDLE file2)
{
    const int BUFFERLENGTH = 1024;

    char buffer1[BUFFERLENGTH] = {'\0'};
    char buffer2[BUFFERLENGTH] = {'\0'};
    DWORD bytesRead = 0;

    BOOL result = ReadFile(file1, buffer1, BUFFERLENGTH - 1, &bytesRead, NULL);
    ThrowLastErrorIf(result == FALSE, string("File1"));

    result = ReadFile(file2, buffer2, BUFFERLENGTH - 1,&bytesRead, NULL);
    ThrowLastErrorIf(result == FALSE, string("File2"));

    string s1(buffer1);
    string s2(buffer2);
    return s1 == s2;
} 

class File
{
private:
    HANDLE handle;

    // Declared but not defined, to avoid double closing.
    File& operator=(const File&);
    File(File&);
public:
    File(const wchar_t* file)
    {
        handle = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, 
            NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
        ThrowLastErrorIf(handle == INVALID_HANDLE_VALUE, GetFileName(file));
    }

    HANDLE Get()
    {
        return handle;
    }

    string GetFileName(const wchar_t* f)
    {
        char buf[1024] = {'\0'};
        wcstombs(buf, f, 1024 -1);
        return string(buf);
    }

    ~File()
    {
        CloseHandle(handle);
    }
};

bool DiffFiles(const wchar_t* file1, const wchar_t* file2) 
{ 

    File f1(file1); 
    File f2(file2); 
    bool result = DiffHandles(f1.Get(), f2.Get()); 
    return result; 
} 


int main()
{
    try
    {
        bool result = DiffFiles(L"file1.txt",
            L"file2.txt");
        if (!result)
        {
            cout << "Files do not match." << "\n";
        }
        else
        {
            cout<< "Files match." << "\n";
        }
    }

    catch(Win32Exception& e)
    {        
        cout << e.what() << "\n";
    }

    cout << "Press any key" << "\n";
    char c;
    cin >> c;
}

调用从非异常代码的异常代码

声明为“extern C”的 C++ 函数可由 C 程序。 C++ COM 服务器可以通过在编写的代码使用任何许多不同的语言。 在实现在非异常代码时要调用的 C++ 的公共异常识别功能,C++ 函数无法允许任何异常传播回调用方。 因此,C++ 函数必须明确地捕获它知道如何处理,因此,如果需要,将异常转换为错误代码调用方了解的每个异常。 如果并非所有潜在的异常知道,C++ 函数应具有 catch(…) 块作为最后一个处理程序。 在这种情况下,因为程序可能置于未知状态,致命错误向调用方报告最好的。

下面的示例演示假定的函数,可能会引发。Win32Exception 或从 std::exception派生的异常类型的所有异常。 该函数捕获这些类型的所有异常并传播错误信息作为 Win32 错误代码调用方。

BOOL DiffFiles2(const wchar_t* file1, const wchar_t* file2) 
{ 
    try 
    { 
        File f1(file1); 
        File f2(file2); 
        if (!DiffHandles(f1.Get(), f2.Get())) 
        { 
            SetLastError(MY_APPLICATION_ERROR_FILE_MISMATCH); 
            return FALSE; 
        } 
        return TRUE; 
    } 
    catch(Win32Exception& e) 
    { 
        SetLastError(e.GetErrorCode()); 
    }

    catch(std::exception& e) 
    { 
        SetLastError(MY_APPLICATION_GENERAL_ERROR); 
    } 
    return FALSE; 
} 

当从异常转换为错误代码时,一个潜在问题是错误代码通常不包含异常可以存储信息的丰富性。 若要解决此问题,可提供 catch 为可能引发的每个特定的异常类型块,并执行日志记录异常的详细信息的记录,在转换成错误代码。 此方法可以创建大量代码重复,如果同一组 catch 块的多个函数都使用。 一种好方法避免代码重复是通过重构这些块来实现 try,并 catch 块的专用实用程序函数并接受在 try 调用块的函数对象。 在每个公共功能,请通过代码来实用程序函数作为 lambda 表达式。

template<typename Func> 
bool Win32ExceptionBoundary(Func&& f) 
{ 
    try 
    { 
        return f(); 
    } 
    catch(Win32Exception& e) 
    { 
        SetLastError(e.GetErrorCode()); 
    } 
    catch(const std::exception& e) 
    { 
        SetLastError(MY_APPLICATION_GENERAL_ERROR); 
    } 
    return false; 
} 

下面的示例演示如何编写定义 functor 的 lambda 表达式。 通过使用 lambda 表达式时,functor 定义“inline”,读取通常比更容易,如果编写为命名函数对象。

bool DiffFiles3(const wchar_t* file1, const wchar_t* file2) 
{ 
    return Win32ExceptionBoundary([&]() -> bool
    { 
        File f1(file1); 
        File f2(file2); 
        if (!DiffHandles(f1.Get(), f2.Get())) 
        { 
            SetLastError(MY_APPLICATION_ERROR_FILE_MISMATCH); 
            return false; 
        } 
        return true; 
    }); 
}

有关 lambda 表达式的更多信息,请参见 在C++中Lambda表达式

请参见

概念

错误和异常处理(现代C++)

如何:异常安全模型