Instrukcje: interfejs między wyjątkowym i niewyjątkowym kodemHow to: Interface between exceptional and non-exceptional code

W tym artykule opisano, jak zaimplementować spójną obsługę wyjątków w module języka C++, a także jak przetłumaczyć te wyjątki na i z kodów błędów na granicach wyjątków.This article describes how to implement consistent exception-handling in a C++ module, and also how to translate those exceptions to and from error codes at the exception boundaries.

Czasami moduł C++ ma interfejs z kodem, który nie używa wyjątków (kod niewyjątkowy).Sometimes a C++ module has to interface with code that doesn't use exceptions (non-exceptional code). Taki interfejs jest znany jako granica wyjątku.Such an interface is known as an exception boundary. Na przykład możesz chcieć wywołać funkcję Win32 CreateFile w programie języka C++.For example, you may want to call the Win32 function CreateFile in your C++ program. CreateFile nie zgłasza wyjątków; Zamiast tego ustawia kody błędów, które mogą być pobierane przez GetLastError funkcję.CreateFile doesn't throw exceptions; instead it sets error codes that can be retrieved by the GetLastError function. Jeśli Twój program w języku C++ nie jest prosty, prawdopodobnie wolisz mieć spójne zasady obsługi błędów oparte na wyjątkach.If your C++ program is non-trivial, then in it you probably prefer to have a consistent exception-based error-handling policy. Prawdopodobnie nie chcesz porzucać wyjątków tylko z powodu interfejsu z niewyjątkowym kodem i nie chcesz mieszać zasad błędów opartych na wyjątkach i nieopartych na wyjątkach w module języka C++.And you probably don't want to abandon exceptions just because you interface with non-exceptional code, and neither do you want to mix exception-based and non-exception-based error policies in your C++ module.

Wywoływanie niewyjątkowych funkcji z C++Calling non-exceptional functions from C++

Gdy wywołujesz niewyjątkową funkcję z C++, pomysłem jest Zawijanie tej funkcji w funkcji języka C++, która wykrywa wszelkie błędy, a następnie może zgłaszać wyjątek.When you call a non-exceptional function from C++, the idea is to wrap that function in a C++ function that detects any errors and then possibly throws an exception. Podczas projektowania takiej funkcji otoki należy najpierw zdecydować, jaki typ gwarancji wyjątku należy podać: nie-throw, Strong lub Basic.When you design such a wrapper function, first decide which type of exception guarantee to provide: no-throw, strong, or basic. Następnie Zaprojektuj funkcję tak, aby wszystkie zasoby, na przykład dojścia do plików, były prawidłowo wydane, jeśli wystąpi wyjątek.Second, design the function so that all resources, for example, file handles, are correctly released if an exception is thrown. Zazwyczaj oznacza to, że do własnych zasobów można używać inteligentnych wskaźników lub podobnych menedżerów zasobów.Typically, this means that you use smart pointers or similar resource managers to own the resources. Aby uzyskać więcej informacji na temat zagadnień związanych z projektowaniem, zobacz How to: Design for Exception Safety.For more information about design considerations, see How to: Design for Exception Safety.

PrzykładExample

W poniższym przykładzie przedstawiono funkcje języka C++, które używają Win32 CreateFile i ReadFile Functions wewnętrznie do otwierania i odczytywania dwóch plików.The following example shows C++ functions that use the Win32 CreateFile and ReadFile functions internally to open and read two files. FileKlasa jest otoką inicjującą (RAII) do obsługi plików.The File class is a resource acquisition is initialization (RAII) wrapper for the file handles. Jego Konstruktor wykrywa warunek "nie znaleziono pliku" i zgłasza wyjątek w celu propagowania błędu w górę stosu wywołań modułu języka C++ (w tym przykładzie main() Funkcja).Its constructor detects a "file not found" condition and throws an exception to propagate the error up the call stack of the C++ module (in this example, the main() function). Jeśli wyjątek jest zgłaszany, gdy File obiekt jest w pełni skonstruowany, destruktor automatycznie wywołuje CloseHandle do zwolnienia dojścia do pliku.If an exception is thrown after a File object is fully constructed, the destructor automatically calls CloseHandle to release the file handle. (Jeśli wolisz, możesz użyć klasy Active Template Library (ATL) CHandle w tym samym przeznaczeniu lub w unique_ptr połączeniu z niestandardowym usuwaniem). Funkcje, które wywołują interfejsy API Win32 i CRT, wykrywają błędy, a następnie generują wyjątki C++ przy użyciu funkcji zdefiniowanej lokalnie ThrowLastErrorIf , która z kolei używa Win32Exception klasy pochodzącej od runtime_error klasy.(If you prefer, you can use the Active Template Library (ATL) CHandle class for this same purpose, or a unique_ptr together with a custom deleter.) The functions that call Win32 and CRT APIs detect errors and then throw C++ exceptions using the locally-defined ThrowLastErrorIf function, which in turn uses the Win32Exception class, derived from the runtime_error class. Wszystkie funkcje w tym przykładzie zapewniają silną gwarancję wyjątku; Jeśli w dowolnym momencie w tych funkcjach wystąpi wyjątek, żadne zasoby nie są wyciekami i nie jest modyfikowany żaden stan programu.All functions in this example provide a strong exception guarantee; if an exception is thrown at any point in these functions, no resources are leaked and no program state is modified.

// 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
    }
}

Wywoływanie wyjątkowego kodu z kodu niewyjątkowegoCalling exceptional code from non-exceptional code

Funkcje języka C++, które są zadeklarowane jako "extern C", mogą być wywoływane przez programy C.C++ functions that are declared as "extern C" can be called by C programs. Serwery C++ COM mogą być używane przez kod zapisany w dowolnej wielu różnych językach.C++ COM servers can be consumed by code written in any of a number of different languages. W przypadku zaimplementowania funkcji publicznych obsługujących wyjątki w języku C++ do wywołania przez kod niewyjątkowy, funkcja C++ nie może zezwalać na żadne wyjątki do propagacji z powrotem do obiektu wywołującego.When you implement public exception-aware functions in C++ to be called by non-exceptional code, the C++ function must not allow any exceptions to propagate back to the caller. W związku z tym, funkcja języka C++ musi zwrócić uwagę na każdy wyjątek, który wie, jak obsłużyć i, w razie potrzeby, przekonwertować wyjątek na kod błędu, który jest rozpoznawany przez wywołującego.Therefore, the C++ function must specifically catch every exception that it knows how to handle and, if appropriate, convert the exception to an error code that the caller understands. Jeśli nie wszystkie potencjalne wyjątki są znane, funkcja C++ powinna mieć catch(...) blok jako ostatnią procedurę obsługi.If not all potential exceptions are known, the C++ function should have a catch(...) block as the last handler. W takim przypadku najlepszym rozwiązaniem jest zgłoszenie błędu krytycznego do obiektu wywołującego, ponieważ program może znajdować się w nieznanym stanie.In such a case, it's best to report a fatal error to the caller, because your program might be in an unknown state.

W poniższym przykładzie pokazano funkcję, która zakłada, że każdy wyjątek, który może zostać wygenerowany jest Win32exception lub typ wyjątku pochodzący z std::exception .The following example shows a function that assumes that any exception that might be thrown is either a Win32Exception or an exception type derived from std::exception. Funkcja przechwytuje dowolny wyjątek tych typów i propaguje informacje o błędzie jako kod błędu Win32 do obiektu wywołującego.The function catches any exception of these types and propagates the error information as a Win32 error code to the caller.

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;
}

W przypadku konwersji z wyjątków na kody błędów, jeden potencjalny problem występuje, że kody błędów często nie zawierają bogatej informacji, które mogą być przechowywane przez wyjątek.When you convert from exceptions to error codes, one potential issue is that error codes often don't contain the richness of information that an exception can store. Aby rozwiązać ten problem, można dostarczyć catch blok dla każdego określonego typu wyjątku, który może zostać wygenerowany, i wykonać rejestrowanie, aby zarejestrować szczegóły wyjątku przed przekonwertowaniem go na kod błędu.To address this, you can provide a catch block for each specific exception type that might be thrown, and perform logging to record the details of the exception before it is converted to an error code. Takie podejście może stworzyć wiele powtórzeń kodu, jeśli wiele funkcji używa tego samego zestawu catch bloków.This approach can create a lot of code repetition if multiple functions all use the same set of catch blocks. Dobrym sposobem uniknięcia powtarzania kodu jest Refaktoryzacja tych bloków w jedną funkcję narzędzia prywatnego, która implementuje try catch bloki i i akceptuje obiekt Function, który jest wywoływany w try bloku.A good way to avoid code repetition is by refactoring those blocks into one private utility function that implements the try and catch blocks and accepts a function object that is invoked in the try block. W każdej funkcji publicznej Przekaż kod do funkcji narzędzia jako wyrażenie lambda.In each public function, pass the code to the utility function as a lambda expression.

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;
}

Poniższy przykład pokazuje, jak napisać wyrażenie lambda definiujące Funktor.The following example shows how to write the lambda expression that defines the functor. Gdy element Funktor jest zdefiniowany jako "inline" przy użyciu wyrażenia lambda, często jest to łatwiejsze do odczytania niż w przypadku, gdy został on zapisany jako nazwany obiekt funkcji.When a functor is defined "inline" by using a lambda expression, it is often easier to read than it would be if it were written as a named function object.

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;
    });
}

Aby uzyskać więcej informacji na temat wyrażeń lambda, zobacz lambda Expressions.For more information about lambda expressions, see Lambda Expressions.

Zobacz teżSee also

Nowoczesne najlepsze rozwiązania w języku C++ dotyczące wyjątków i obsługi błędówModern C++ best practices for exceptions and error handling
Instrukcje: projektowanie pod kątem bezpieczeństwa wyjątkówHow to: Design for Exception Safety