方法: 例外的なコードと非例外的なコードをインターフェイスで連結する
ここでは、. C++ モジュールの一貫した例外処理を実装する方法を、例外の境界のエラー コードとの間でこれらの例外を変換する方法を説明します。
.場合、C++ モジュールは、例外 (いない例外的なコード) を使用しないコードとやり取りする必要があります。このようなインターフェイスは、例外の境界と呼ばれます。たとえば、の C++ プログラムの Win32 関数を呼び出して CreateFile することもできます。CreateFile、例外がスローされない; 代わりに、GetLastError 関数で取得できるエラー コードを設定します。次の C++ プログラムが重要である場合でも一貫した例外ベースのエラー処理ポリシーを設定します。単にいない例外的なコードとやり取りする、いずれも、C++ 例外のモジュールのベースと非ベースの例外エラー ポリシーが混在したくため、例外を破棄しないします。
C++ の例外的ではないな関数を呼び出して
C++ の例外的ではないな関数を呼び出すときに、これは、エラーを検出し、場合に例外をスローする C ++.の関数の関数をラップすることです。このようなラッパー関数をデザインする場合、用意されている例外の種類により先に決定する: 厳密に、基本または非スローします。2 番目に例外がスローされるすべてのリソース (たとえば、ファイル ハンドルが、正しく解放されるように、関数をデザインします。通常、これは独自にスマート ポインターまたは同様のリソースをリソース マネージャー使用することを意味します。デザインに関する考慮事項の詳細については、方法: 例外安全性に対応した設計をする"を参照してください。
例
次の例では、2 種類のファイルを開いて読み取るために使用する Win32 関数が C++ CreateFile と ReadFile 内部的に機能する示します。File のクラスは、Resource Acquisition Is Initialization、Resource Acquisition Is コンストラクターの組み合わせ Application Programming Interface がファイル ハンドルの初期化の (RAII) ラッパーです。そのコンストラクターは "file" の検索条件を検出し、C++ モジュールのコール スタックの上部のエラーを反映させる場合に例外をスローします。File のオブジェクトが完全に作成されると例外がスローされた場合、デストラクターは自動的にファイル ハンドルを解放するために CloseHandle を呼び出します。また、この目的のために同じ Active Template Library) の (ATL) CHandle のクラス、またはカスタムの削除子とともに unique_ptr を使用できます)。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;
}
例外からエラー コードに変換すると、1 種類の潜在的な問題は、エラー コードが頻繁に例外が格納できる情報の多様性がないことです。これを解決するには、それぞれの特定の例外の種類にエラー コードに変換される前に catch ブロックを提供するとスローされる可能性のある例外の詳細を記録するログを実行します。この方法は、複数のすべての使用 catch のセットがブロック作用すると、コードを繰り返しを作成できます。コード繰り返しを回避の方法は try と catch のブロックを実行する実行し、try ブロックに呼び出される関数オブジェクトを受け取ります。1 種類のプライベート ユーティリティ関数にリファクタリングによってこれらのブロック。各パブリック関数では、ラムダ式としてユーティリティ関数にコードを渡します。
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;
}
次の例ではファンクタを定義するラムダ式を記述する方法を示します。ファンクタは、ラムダ式を使用して定義された "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;
});
}
ラムダ式の詳細については、「C++ でのラムダ式」を参照してください。