如何:异常和非异常代码之间的接口
本文介绍如何实现一致异常处理在 c. c++ 模块,以及如何向/从错误代码将这些异常在异常边界。
有时 c. c++ 模块必须与不使用异常的代码的接口 (非异常代码)。 此类接口称为" 异常边界。 例如,您可能要对您的 C++ 程序的 Win32 函数 CreateFile。 CreateFile 不会引发异常;而设置可以由 GetLastError 函数检索的错误代码。 如果您的 C++ 程序很重要,则在您可能希望让一致的基于异常的错误处理的策略。 正因为您与非异常代码的接口和都不希望组合在 C++ 模块,的基于异常及基于非异常的错误策略您可能不希望放弃异常。
从 C++ 调用非异常功能
当您调用从 C++ 时的非异常功能,则该目的是包装在检测所有错误。然后引发异常的 c. c++ 函数的函数。 当设计这样的包装函数时,首先确定异常确保哪种类型提供的:NO-引发,强烈或基。 接下来,设计功能,以便,正确释放所有资源,例如,文件句柄,如果引发了异常。 通常,这意味着您使用智能指针或类似的资源管理器来自己资源。 有关设计注意事项的更多信息,请参见 如何:异常安全模型。
示例
下面的示例演示使用的 C++ 函数 Win32 CreateFile 和 ReadFile 在内部函数打开并读取两个文件。 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表达式。