Aracılığıyla paylaş


Nasıl yapılır: Özel durum güvenliği için tasarım

Özel durum mekanizmasının avantajlarından biri, yürütmenin özel durum hakkındaki verilerle birlikte, özel durumu işleyen ilk catch deyimine özel durum oluşturan deyiminden doğrudan atlamasıdır. İşleyici, çağrı yığınında herhangi bir sayıda düzey olabilir. Try deyimi ile throw deyimi arasında çağrılan işlevlerin, oluşan özel durum hakkında hiçbir şey bilmesi gerekmez. Ancak, bir özel durumun aşağıdan yayılabildiği herhangi bir noktada kapsamı "beklenmedik şekilde" aşabilecek şekilde tasarlanmaları ve bunu kısmen oluşturulmuş nesneleri, sızdırılan belleği veya kullanılamayan durumlardaki veri yapılarını geride bırakmadan gerçekleştirmeleri gerekir.

Temel teknikler

Sağlam bir özel durum işleme ilkesi dikkatli bir düşünce gerektirir ve tasarım sürecinin bir parçası olmalıdır. Genel olarak, çoğu özel durum algılanır ve bir yazılım modülünün alt katmanlarında oluşturulur, ancak genellikle bu katmanların hatayı işlemek veya bir iletiyi son kullanıcılara göstermek için yeterli bağlamı yoktur. Orta katmanlarda işlevler, özel durum nesnesini incelemek zorunda olduklarında bir özel durumu yakalayıp yeniden oluşturabilir veya en sonunda özel durumu yakalayan üst katman için sağlayabilecekleri ek yararlı bilgilere sahiptir. Bir işlev yalnızca tamamen kurtarabiliyorsa bir özel durumu yakalamalı ve "yutmalıdır". Çoğu durumda, orta katmanlardaki doğru davranış, bir özel durumun çağrı yığınını yaymasına izin vermektir. En yüksek katmanda bile, özel durum programı doğruluğu garanti edilemeyen bir durumda bırakırsa, işlenmeyen bir özel durumun programı sonlandırmasına izin vermek uygun olabilir.

bir işlev bir özel durumu nasıl işlerse işlesin, "özel durum açısından güvenli" olduğunu garanti etmeye yardımcı olmak için aşağıdaki temel kurallara göre tasarlanmalıdır.

Kaynak sınıflarını basit tutun

Sınıflarda el ile kaynak yönetimini kapsüllediğinizde, tek bir kaynağı yönetmek dışında hiçbir şey içermeyen bir sınıf kullanın. Sınıfı basit tutarak kaynak sızıntıları oluşturma riskini azaltırsınız. Aşağıdaki örnekte gösterildiği gibi mümkün olduğunda akıllı işaretçileri kullanın. Bu örnek, kullanıldığında farkları shared_ptr vurgulamak için kasıtlı olarak yapay ve basittir.

// old-style new/delete version
class NDResourceClass {
private:
    int*   m_p;
    float* m_q;
public:
    NDResourceClass() : m_p(0), m_q(0) {
        m_p = new int;
        m_q = new float;
    }

    ~NDResourceClass() {
        delete m_p;
        delete m_q;
    }
    // Potential leak! When a constructor emits an exception,
    // the destructor will not be invoked.
};

// shared_ptr version
#include <memory>

using namespace std;

class SPResourceClass {
private:
    shared_ptr<int> m_p;
    shared_ptr<float> m_q;
public:
    SPResourceClass() : m_p(new int), m_q(new float) { }
    // Implicitly defined dtor is OK for these members,
    // shared_ptr will clean up and avoid leaks regardless.
};

// A more powerful case for shared_ptr

class Shape {
    // ...
};

class Circle : public Shape {
    // ...
};

class Triangle : public Shape {
    // ...
};

class SPShapeResourceClass {
private:
    shared_ptr<Shape> m_p;
    shared_ptr<Shape> m_q;
public:
    SPShapeResourceClass() : m_p(new Circle), m_q(new Triangle) { }
};

Kaynakları yönetmek için RAII deyimini kullanma

Bir işlevin özel durum açısından güvenli olması için, kullanılarak malloc ayrılan veya new yok edilen nesnelerin ve bir özel durum oluşturulsa bile dosya tanıtıcıları gibi tüm kaynakların kapatılmasını veya serbest bırakılmasını sağlaması gerekir. Kaynak Alımı Başlatmadır (RAII) deyimi, bu tür kaynakların yönetimini otomatik değişkenlerin kullanım ömrüyle bağlar. Bir işlev normal olarak döndürerek veya bir özel durum nedeniyle kapsamın dışına çıktığında, tam olarak yapılandırılmış tüm otomatik değişkenlerin yıkıcıları çağrılır. Akıllı işaretçi gibi bir RAII sarmalayıcı nesnesi, yıkıcısında uygun delete veya close işlevini çağırır. Özel durum güvenli kodda, her kaynağın sahipliğini hemen bir tür RAII nesnesine geçirmek kritik önem taşır. vector, , string, make_shared, fstreamve benzeri sınıfların kaynağı sizin için edinme işlemini işlediğini unutmayın. Ancak, unique_ptr geleneksel shared_ptr yapılar özeldir çünkü kaynak alımı nesne yerine kullanıcı tarafından gerçekleştirilir; bu nedenle, Kaynak Yayını Yok Edilir olarak sayılır, ancak RAII olarak sorgulanabilir.

Üç özel durum garanti eder

Genellikle özel durum güvenliği, bir işlevin sağlayabilecekleri üç özel durum garantisi açısından ele alınır: başarısız olmayan garanti, güçlü garanti ve temel garanti.

Hata yok garantisi

Bir işlevin sağlayabilecekleri en güçlü garanti, başarısız değil (veya "fırlatmama") garantisidir. İşlevin bir özel durum oluşturmayacağını veya yayılmasına izin verileceğini belirtir. Ancak, (a) bu işlevin çağırdığı tüm işlevlerin de başarısız olmadığını bilmiyorsanız veya (b) bu işleve ulaşmadan önce oluşan özel durumların yakalandığını bilmiyorsanız veya (c) bu işleve ulaşabilecek tüm özel durumları yakalamayı ve doğru şekilde işlemeyi bilmiyorsanız, böyle bir garantiyi güvenilir bir şekilde sağlayamazsınız.

Hem güçlü garanti hem de temel garanti, yıkıcıların başarısız olduğu varsayımını temel alır. Standart Kitaplıktaki tüm kapsayıcılar ve türler, yıkıcılarının oluşturmamasını garanti eder. Ters bir gereksinim de vardır: Standart Kitaplık, buna verilen kullanıcı tanımlı türlerin (örneğin, şablon bağımsız değişkenleri) oluşturmayan yıkıcılara sahip olmasını gerektirir.

Güçlü garanti

Güçlü garanti, bir işlevin bir özel durum nedeniyle kapsam dışına çıkması durumunda belleğin sızmayacağını ve program durumunun değiştirilmeyeceğini belirtir. Güçlü bir garanti sağlayan bir işlev temelde işleme veya geri alma semantiğine sahip bir işlemdir: tamamen başarılı olur veya hiçbir etkisi yoktur.

Temel garanti

Temel garanti, üçünden en zayıfıdır. Ancak, güçlü bir garanti bellek tüketiminde veya performansta çok pahalı olduğunda en iyi seçenek olabilir. Temel garanti, bir özel durum oluşursa hiçbir belleğin sızdırılmadığını ve veriler değiştirilmiş olsa bile nesnenin hala kullanılabilir durumda olduğunu belirtir.

Özel durum güvenli sınıflar

Bir sınıf, güvenli olmayan işlevler tarafından kullanıldığında bile kendi özel durum güvenliğini sağlamaya yardımcı olabilir, bunun için kendinin kısmen yapılandırılmasını veya kısmen yok edilmesini engelleyebilir. Bir sınıf oluşturucu tamamlanmadan önce çıkarsa, nesne hiçbir zaman oluşturulmaz ve yok edicisi hiçbir zaman çağrılmaz. Özel durumdan önce başlatılan otomatik değişkenlerin yıkıcıları çağrılır, ancak dinamik olarak ayrılmış bellek veya akıllı işaretçi veya benzer bir otomatik değişken tarafından yönetilmeyen kaynaklar sızdırılır.

Yerleşik türlerin tümü başarısız değildir ve Standart Kitaplık türleri temel garantiyi en az destekler. Özel durum açısından güvenli olması gereken kullanıcı tanımlı herhangi bir tür için şu yönergeleri izleyin:

  • Tüm kaynakları yönetmek için akıllı işaretçileri veya diğer RAII türü sarmalayıcıları kullanın. Oluşturucu bir özel durum oluşturursa yıkıcı çağrılmadığı için sınıf yıkıcınızda kaynak yönetimi işlevselliğinden kaçının. Ancak, sınıfı yalnızca bir kaynağı denetleen ayrılmış bir kaynak yöneticisiyse, kaynakları yönetmek için yıkıcıyı kullanmak kabul edilebilir.

  • Temel sınıf oluşturucusunda oluşan bir özel durumun türetilmiş bir sınıf oluşturucusunda yutulamayacağını anlayın. Türetilmiş bir oluşturucuda temel sınıf özel durumunu çevirmek ve yeniden oluşturmak istiyorsanız bir işlev try bloğu kullanın.

  • Özellikle bir sınıfın "başarısız olmasına izin verilen başlatma" kavramı varsa, tüm sınıf durumunu akıllı işaretçiye sarmalanmış bir veri üyesinde depolayıp depolamayabilirsiniz. C++ başlatılmamış veri üyelerine izin verse de, başlatılmamış veya kısmen başlatılmış sınıf örneklerini desteklemez. Oluşturucu başarılı veya başarısız olmalıdır; oluşturucu tamamlanmaya kadar çalışmazsa hiçbir nesne oluşturulmaz.

  • Herhangi bir özel durumun bir yıkıcıdan kaçmasına izin verme. C++ için temel bir aksiyom, yıkıcıların çağrı yığınını yaymak için hiçbir zaman bir özel duruma izin vermemesidir. Bir yıkıcının olası bir özel durum oluşturma işlemi gerçekleştirmesi gerekiyorsa, bunu bir catch bloğunda yapmalı ve özel durumu yutmalıdır. Standart kitaplık, tanımladığı tüm yıkıcılarda bu garantiyi sağlar.

Ayrıca bkz.

Özel durumlar ve hata işleme için modern C++ en iyi yöntemleri
Nasıl yapılır: Özel Durumlu Kod ve Özel Durumlu Olmayan Kod Arasında Arabirim