Postupy: Rozhraní mezi kódem výjimek a ostatním kódem
Tento článek popisuje, jak implementovat konzistentní zpracování výjimek v modulu jazyka C++ a také způsob převodu těchto výjimek z chybových kódů na hranicích výjimek.
Někdy musí modul C++ pracovat s kódem, který nepoužívá výjimky (kód bez výjimek).Toto rozhraní se nazývá hranice výjimky.Například můžete chtít volat funkci Win32 CreateFile v programu C++.CreateFile nevyvolá výjimky; místo toho definuje kódy chyb, které mohou být načteny funkcí GetLastError.Pokud C++ program je netriviální, potom v něm pravděpodobně dáváte přednost jednotným zásadám zpracování chyb na základě výjimky.A pravděpodobně nebudete chtít opustit výjimky jen proto, že používáte rozhraní s nevýjimečným kódem, a ani nebudete chtít míchat zásady chyb na základě výjimky a na jiném základě v modulu jazyka C++.
Volání nevýjimečné funkce z jazyka C++
Při volání funkce bez výjimek z jazyka C++, myšlenka je zabalit tuto funkci do funkce jazyka C++, která zjistí všechny chyby a pak případně vyvolá výjimku.Při návrhu takové funkce obálky nejprve zvolte, který typ záruky výjimky chcete poskytnout: bez vyvolávání, silný nebo základní.Za druhé navrhněte funkci tak, aby všechny zdroje, například obslužné rutiny souborů, byly správně uvolněny, pokud je vyvolána výjimka.Obvykle to znamená použít inteligentní ukazatele nebo podobné správce prostředků pro vlastnění zdrojů.Další informace o zvažování návrhu naleznete v tématu Postupy: Návrh s ohledem na bezpečnost výjimek.
Příklad
Následující příklad ukazuje funkce jazyka C++, které používají funkce rozhraní Win32 CreateFile a ReadFile interně pro otevření a čtení dvou souborů. Třída File je obálka inicializace získání prostředků (RAII) pro popisovače souborů.Jeho konstruktor rozpozná stav „soubor nebyl nalezen“ a vyvolá výjimku, aby se chyba rozšířila nahoru až k zásobníku volání modulu C++ (v tomto příkladu funkce main()).Pokud je vyvolána výjimka po úplném vytvoření objektu File, destruktor automaticky volá CloseHandle za účelem uvolnění popisovače souboru. (Pokud chcete, můžete použít třídu aktivní šablony knihovny (ATL) CHandle pro stejný účel, nebo unique_ptr spolu s vlastním odstraňovačem.) Funkce, které volají rozhraní API Win32 a CRT detekují chyby a poté vyvolají výjimky C++ pomocí místně definované funkce ThrowLastErrorIf, která dále používá třídu Win32Exception odvozenou z třídy runtime_error.Všechny funkce v tomto příkladu poskytnou záruku silné výjimky; pokud v libovolném bodě těchto funkcí je vyvolána výjimka, nedojde k přetečení žádných zdrojů a není upraven žádný stav programu.
// compile with: /EHsc
#include <Windows.h>
#include <stdlib.h>
#include <vector>
#include <iostream>
#include <string>
#include <limits>
#include <stdexcept>
using namespace std;
string FormatErrorMessage(DWORD error, const string& msg)
{
static const int BUFFERLENGTH = 1024;
vector<char> buf(BUFFERLENGTH);
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, 0, error, 0, buf.data(),
BUFFERLENGTH - 1, 0);
return string(buf.data()) + " (" + msg + ")";
}
class Win32Exception : public runtime_error
{
private:
DWORD m_error;
public:
Win32Exception(DWORD error, const string& msg)
: runtime_error(FormatErrorMessage(error, msg)), m_error(error) { }
DWORD GetErrorCode() const { return m_error; }
};
void ThrowLastErrorIf(bool expression, const string& msg)
{
if (expression) {
throw Win32Exception(GetLastError(), msg);
}
}
class File
{
private:
HANDLE m_handle;
// Declared but not defined, to avoid double closing.
File& operator=(const File&);
File(const File&);
public:
explicit File(const string& filename)
{
m_handle = CreateFileA(filename.c_str(), GENERIC_READ, FILE_SHARE_READ,
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, nullptr);
ThrowLastErrorIf(m_handle == INVALID_HANDLE_VALUE,
"CreateFile call failed on file named " + filename);
}
~File() { CloseHandle(m_handle); }
HANDLE GetHandle() { return m_handle; }
};
size_t GetFileSizeSafe(const string& filename)
{
File fobj(filename);
LARGE_INTEGER filesize;
BOOL result = GetFileSizeEx(fobj.GetHandle(), &filesize);
ThrowLastErrorIf(result == FALSE, "GetFileSizeEx failed: " + filename);
if (filesize.QuadPart < (numeric_limits<size_t>::max)()) {
return filesize.QuadPart;
} else {
throw;
}
}
vector<char> ReadFileVector(const string& filename)
{
File fobj(filename);
size_t filesize = GetFileSizeSafe(filename);
DWORD bytesRead = 0;
vector<char> readbuffer(filesize);
BOOL result = ReadFile(fobj.GetHandle(), readbuffer.data(), readbuffer.size(),
&bytesRead, nullptr);
ThrowLastErrorIf(result == FALSE, "ReadFile failed: " + filename);
cout << filename << " file size: " << filesize << ", bytesRead: "
<< bytesRead << endl;
return readbuffer;
}
bool IsFileDiff(const string& filename1, const string& filename2)
{
return ReadFileVector(filename1) != ReadFileVector(filename2);
}
#include <iomanip>
int main ( int argc, char* argv[] )
{
string filename1("file1.txt");
string filename2("file2.txt");
try
{
if(argc > 2) {
filename1 = argv[1];
filename2 = argv[2];
}
cout << "Using file names " << filename1 << " and " << filename2 << endl;
if (IsFileDiff(filename1, filename2)) {
cout << "*** Files are different." << endl;
} else {
cout<< "*** Files match." << endl;
}
}
catch(const Win32Exception& e)
{
ios state(nullptr);
state.copyfmt(cout);
cout << e.what() << endl;
cout << "Error code: 0x" << hex << uppercase << setw(8) << setfill('0')
<< e.GetErrorCode() << endl;
cout.copyfmt(state); // restore previous formatting
}
}
Volání výjimečného kódu s nevýjimečného kódu
Funkce jazyka C++, které jsou deklarovány jako "extern C", mohou být volány programy C.Servery C++ COM mohou být zaplněny kódem napsaným v některém z mnoha různých jazyků.Při implementaci veřejných funkcí podporujících výjimky v jazyce C++, které mají být volány kódem bez výjimek, nesmí funkce C++ dovolit propagaci žádných výjimek zpět do volajícího objektu.Funkce C++ proto musí konkrétně zachytit každou výjimku, kterou umí zpracovat a v případě potřeby ji převést do srozumitelného chybového kódu, kterému volající rozumí.Pokud nejsou známy všechny potenciální výjimky, měla by mít funkce C++ blok catch(…) jako poslední obslužnou rutinu.V takovém případě je nejlepší oznámit závažnou chybu volajícímu, protože váš program může být v neznámém stavu.
Následující příklad ukazuje funkci, která předpokládá, že jakákoliv výjimka, která může být vyvolána je buď Win32Exception, nebo typ výjimky odvozený z std::exception.Funkce zachytí jakoukoli výjimku těchto typů a šíří informaci o chybě jako chybový kód Win32 k volajícímu.
BOOL DiffFiles2(const string& file1, const string& file2)
{
try
{
File f1(file1);
File f2(file2);
if (IsTextFileDiff(f1, f2))
{
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;
}
Při převádění výjimek na chybové kódy jedním potenciálním problémem je, že kódy chyb často neobsahují bohatost informací, které mohou ukládat výjimky.Chcete-li to vyřešit, můžete poskytnout blok catch pro každý typ výjimky, která může být vyvolána a provádět protokolování pro zaznamenání podrobností o výjimce, než je převedena na chybový kód.Tento přístup může vytvořit velké množství opakování kódu, pokud více funkcí používá stejnou sadu bloků catch.Dobrým způsobem, jak zabránit opakování kódu, je refaktoring bloků do jedné soukromé funkce nástroje, která implementuje bloky try a catch a přijímá objekt funkce, který je vyvolán v bloku try.V každé veřejné funkci předejte kód funkci nástroje jako lambda výraz.
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;
}
Následující příklad ukazuje, jak zapsat lambda výraz, který definuje funktor.Když je funktor definovaný „vloženě“ pomocí lambda výrazu, je to často čitelnější než by byl, pokud by byl zapsán jako objekt pojmenované funkce.
bool DiffFiles3(const string& file1, const string& file2)
{
return Win32ExceptionBoundary([&]() -> bool
{
File f1(file1);
File f2(file2);
if (IsTextFileDiff(f1, f2))
{
SetLastError(MY_APPLICATION_ERROR_FILE_MISMATCH);
return false;
}
return true;
});
}
Další informace o výrazech lambda naleznete v tématu Výrazy lambda v jazyce C++.