Aracılığıyla paylaş


Nasıl yapılır: Olağanüstü ve istisnai olmayan kodlar arasındaki arabirim

Bu makalede, C++ kodunda tutarlı özel durum işlemenin nasıl uygulandığı ve özel durum sınırlarında hata kodlarına ve hata kodlarından özel durumların nasıl çevrildiği açıklanır.

Bazen C++ kodunun özel durumlar (istisnai olmayan kod) kullanmayan kodla arabirime sahip olması gerekir. Böyle bir arabirim özel durum sınırı olarak bilinir. Örneğin, C++ programınızda Win32 işlevini CreateFile çağırmak isteyebilirsiniz. CreateFile özel durum oluşturmaz. Bunun yerine işlev tarafından GetLastError alınabilecek hata kodlarını ayarlar. C++ programınız önemsiz değilse, büyük olasılıkla tutarlı bir özel durum tabanlı hata işleme ilkesine sahip olmak istersiniz. Ayrıca, istisnai olmayan kodlarla arabirim oluşturduğunuz için büyük olasılıkla özel durumları bırakmak istemezsiniz. Ayrıca, C++ kodunuzda özel durum tabanlı ve özel durum tabanlı olmayan hata ilkelerini karıştırmak istemezsiniz.

C++'tan olağanüstü olmayan işlevleri çağırma

C++'tan istisnai olmayan bir işlevi çağırdığınızda, bu işlevi hataları algılayan ve ardından büyük olasılıkla bir özel durum oluşturan bir C++ işlevine sarmalamanız gerekir. Böyle bir sarmalayıcı işlevi tasarlarken, önce hangi tür özel durum garantisi sağlayacağınıza karar verin: noexcept, strong veya basic. İkincisi, işlevi, bir özel durum oluşursa tüm kaynakların ,örneğin dosya tanıtıcılarının doğru şekilde serbest bırakıldığı şekilde tasarlayın. Genellikle, kaynakların sahibi olmak için akıllı işaretçileri veya benzer kaynak yöneticilerini kullandığınız anlamına gelir. Tasarımla ilgili dikkat edilmesi gerekenler hakkında daha fazla bilgi için bkz . Nasıl yapılır: Özel durum güvenliği için tasarım.

Örnek

Aşağıdaki örnekte, win32 CreateFile kullanan C++ işlevleri ve ReadFile dahili olarak iki dosyayı açıp okumak için işlevler gösterilmektedir. File sınıfı, dosya tanıtıcıları için kaynak alımı başlatma (RAII) sarmalayıcıdır. Oluşturucu bir "dosya bulunamadı" koşulu algılar ve hatayı C++ yürütülebilir dosyasının çağrı yığınına yaymak için bir özel durum oluşturur (bu örnekte main() işlev). Bir nesne tam olarak oluşturulduktan sonra bir File özel durum oluşursa, yıkıcı otomatik olarak dosya tanıtıcısını serbest bırakmak için çağırır CloseHandle . (İsterseniz, aynı amaç için Etkin Şablon Kitaplığı (ATL) CHandle sınıfını veya unique_ptr özel silme işleviyle birlikte kullanabilirsiniz.) Win32 ve CRT API'lerini çağıran işlevler hataları algılar ve ardından yerel olarak tanımlanan ThrowLastErrorIf ve sınıfından türetilen runtime_error sınıfı kullanan Win32Exception C++ özel durumları oluşturur. Bu örnekteki tüm işlevler güçlü bir özel durum garantisi sağlar: Bu işlevlerin herhangi bir noktasında özel durum oluşturulursa, hiçbir kaynak sızdırılır ve program durumu değiştirilmez.

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

Olağanüstü olmayan koddan olağanüstü kod çağırma

olarak extern "C" bildirilen C++ işlevleri C programları tarafından çağrılabilir. C++ COM sunucuları, herhangi bir sayıda farklı dilde yazılmış kod tarafından kullanılabilir. C++ içinde istisnai olmayan kod tarafından çağrılmak üzere genel özel durum algılamalı işlevler uyguladığınızda, C++ işlevi çağırana geri yayılması için hiçbir özel duruma izin vermemelidir. Bu tür arayanların C++ özel durumlarını yakalaması veya işlemesi hiçbir yolu yoktur. Program sonlandırabilir, kaynakları sızdırabilir veya tanımsız davranışlara neden olabilir.

C++ işlevinizin özellikle nasıl işleneceğini bildiği her özel durumu yakalamasını extern "C" ve uygunsa özel durumu çağıranın anladığı bir hata koduna dönüştürmesini öneririz. Tüm olası özel durumlar bilinmiyorsa, C++ işlevinin son işleyici olarak bir catch(...) bloğu olmalıdır. Böyle bir durumda, programınız bilinmeyen ve kurtarılamaz durumda olabileceğinden, arayana önemli bir hata bildirmek en iyisidir.

Aşağıdaki örnek, oluşturulabilecek herhangi bir özel durumun veya öğesinden std::exceptiontüretilmiş bir özel durum türü olduğunu Win32Exception varsayar. İşlev, bu türlerdeki herhangi bir özel durumu yakalar ve hata bilgilerini çağırana Win32 hata kodu olarak yar.

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

Özel durumlardan hata kodlarına dönüştürdüğünüzde olası bir sorun oluşur: Hata kodları genellikle bir özel durumun depolayabileceğiniz bilgilerin zenginliğini içermez. Bu sorunu gidermek için, oluşturulabilecek her özel durum türü için bir catch blok sağlayabilir ve hata koduna dönüştürülmeden önce özel durumun ayrıntılarını kaydetmek için günlüğe kaydetme işlemi gerçekleştirebilirsiniz. Birden çok işlevin tümü aynı blok kümesini catch kullanıyorsa, bu yaklaşım yinelenen kod oluşturabilir. Kod yinelemesini önlemenin iyi bir yolu, bu blokları ve catch bloklarını try uygulayan ve bloğunda çağrılan bir işlev nesnesini kabul eden bir özel yardımcı program işlevinde try yeniden düzenlemektir. Her genel işlevde, kodu yardımcı program işlevine lambda ifadesi olarak geçirin.

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

Aşağıdaki örnek, functor'ı tanımlayan lambda ifadesinin nasıl yazıldığını gösterir. Lambda ifadesini satır içinde okumak, adlandırılmış işlev nesnesini çağıran koddan daha kolaydır.

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

Lambda ifadeleri hakkında daha fazla bilgi için bkz . Lambda ifadeleri.

Olağanüstü koddan olağanüstü olmayan kod aracılığıyla olağanüstü kod çağırma

Özel durum bilgisi olmayan kodlar arasında özel durumlar oluşturması mümkündür ancak önerilmez. Örneğin, C++ programınız sağladığınız geri çağırma işlevlerini kullanan bir kitaplığı çağırabilir. Bazı durumlarda, özgün çağıranın işleyebileceği istisnai olmayan kod genelinde geri çağırma işlevlerinden özel durumlar oluşturabilirsiniz. Ancak, özel durumların başarıyla çalışabileceği durumlar katıdır. Kitaplık kodunu yığın geri sarma semantiğini koruyacak şekilde derlemeniz gerekir. Özel durum bilgisi olmayan kod, C++ özel durumunu tuzağa düşürebilecek hiçbir şey yapamaz. Ayrıca, çağıran ile geri arama arasındaki kitaplık kodu yerel kaynakları ayıramaz. Örneğin, özel durum bilgisi olmayan kodun ayrılmış yığın belleğine işaret eden yerel öğeleri olamaz. Yığın kaldırıldığında bu kaynaklar sızdırılır.

Özel durum tanımayan kodlar arasında özel durumlar oluşturabilmek için bu gereksinimlerin karşılanması gerekir:

  • kullanarak /EHsözel durum algılamalı olmayan kod genelinde kod yolunun tamamını oluşturabilirsiniz.
  • Yığın kaldırıldığında sızabilecek yerel olarak ayrılmış kaynak yok,
  • Kodun tüm özel durumları yakalayan yapılandırılmış özel __except durum işleyicileri yoktur.

Olağanüstü olmayan kodda özel durumların atılması hataya açıktır ve hata ayıklama zorluğuna neden olabileceği için bunu önermiyoruz.

Ayrıca bkz.

Özel durumlar ve hata işleme için modern C++ en iyi yöntemleri
Nasıl yapılır: Özel durum güvenliği için tasarım