How to: 例外和非例外的程式碼之間的介面
本文說明如何在 C ++. 模組的一致的例外狀況處理,以及如何將錯誤碼轉譯這些例外狀況會在例外狀況界限。
有時候 C ++. 模組必須配合不使用例外狀況的程式碼 (非例外狀況程式碼)。這類介面稱為 例外狀況界限。例如,可以呼叫 Win32 函式在您的 C++ 程式的 CreateFile 。CreateFile 不會擲回例外狀況;而設定可由 GetLastError 函式擷取的錯誤碼。如果您的 C++ 程式很重要,則在您可能想讓一項一致的以例外狀況的錯誤處理原則。您可能不想要中止例外狀況因為您正在使用非例外狀況程式碼,因此,兩者都不要混合在您的 C++ 模組以例外狀況的和以非例外狀況的錯誤原則。
呼叫從 C++ 的非例外函式
當您呼叫從 C++ 時的非例外函式,這個概念是包裝在偵測到的所有錯誤可以擲回例外狀況的 C ++. 函式的函式。當您設計這類包裝函式時,請先判斷例外狀況保證哪種型別提供的:無擲回,或基礎。其次,根據設計,函式,以便正確地釋放任何資源,例如,檔案控制代碼,則擲回例外狀況。通常,這表示您使用智慧型指標或類似的資源管理員要擁有資源。如需設計考量的詳細資訊,請參閱 How to: 設計例外狀況的安全。
範例
下列範例顯示使用的 C++ 函式 Win32 CreateFile 和 ReadFile 內部函式開啟和讀取兩個檔案。File 類別是資源擷取為初始設定檔案中處理 (RAII) 的包裝函式。其建構函式偵測到找不到檔案條件並擲回例外狀況傳播 C++ 模組的呼叫堆疊的錯誤。如果擲回例外狀況, File 物件完全建構之後,解構函式自動呼叫 CloseHandle 釋放檔案控制代碼。(如果您想要的話,您可以為這個相同的目的使用 Active Template Library (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;
}
呼叫從非例外狀況程式碼的例外處理
宣告為「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;
}
下列範例顯示如何撰寫定義功能的 Lambda 運算式。使用 Lambda 運算式時,在功能上定義的「in」,讀取比通常比較容易,如果寫入為具名函式物件。
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 運算式。