2

例外がスローされたときにクリーンアップが必要なことをしているとしましょう。

たとえば、動的配列を作成していて、オブジェクトを構築する必要があるとしますが、それらのコンストラクターが例外をスローする可能性があります。

size_t const n = 100;
T *const p = static_cast<T *>(operator new(sizeof(T) * n));
size_t i;
for (i = 0; i < n; ++i)
    new (&p[i]) T(1, 2, 3);      // Not exception-safe if T::T(T const &) throws!

次のいずれかで修正できますcatch (...) { ...; throw; }

size_t const n = 100;
T *const p = static_cast<T *>(operator new(sizeof(T) * n));
size_t i;
try
{
    for (i = 0; i < n; ++i)
        new (&p[i]) T(1, 2, 3);
}
catch (...)
{
    while (i > 0)
        p[--i].~T();
    operator delete(p);
    throw;
}

またはスコープ付きデストラクタを介して:

size_t n = 100;
struct Guard
{
    T *p;
    size_t i;
    Guard(size_t n) : i(), p(static_cast<T *>(operator new(sizeof(T) * n))) { }
    ~Guard()
    {
        while (i > 0)
            p[--i].~T();
        operator delete(p);
    }
} guard(n);

for (guard.i = 0; guard.i < n; ++guard.i)
    new (&guard.p[guard.i]) T(1, 2, 3);

guard.i = 0;     // Successful... "commit" the changes
guard.p = NULL;  // or whatever is necessary to commit the changes

いつ、どの手法を使用するのがよいでしょうか? またその理由は?

(注:この例は、2 つの手法の違いを示すことのみを目的としています。完全なコードではないことはわかっているため、この特定の例に注目しないください。説明のためだけです。)

4

2 に答える 2

2

デストラクタを使用したソリューションは、明示的なソリューションよりも優れていtry/catchます。

  • 再利用可能です。別の関数で同様の初期化を行う必要がある場合は、同じガード クラスを再利用できます。
  • 維持するのが簡単です-ある状況で関数が失敗して戻る必要があるが、例外はスローされないとしましょう。ガードクラスを使用すると、多かれ少なかれ自動的に処理されます
  • コードがよりモジュール化されているため、よりクリーンです
于 2014-03-12T09:57:51.393 に答える
2

一般に、それはスケーリング安全性の問題だと思います。

問題try/catchは 2 つあります。

  • 安全性の問題: (どういうわけか) を無視する早期復帰はcatchクリーンアップに失敗します
  • スケーリングの問題: ネストされたtry/catchブロックがコードを混乱させる
  • スコープの問題:catch変数でアクセスできるようにするには、 の前に定義する必要があるtryため、デフォルトの構築/nullability がサポートされます。それは痛いことができます

代わりに、遅延ステートメントとガードは不要なブロック/スコープを作成せず、したがってインデントを作成せず、直線的に読み取ります。

例:

char buffer1[sizeof(T)];
try {
    new (buffer1) T(original);

    char buffer2[sizeof(T)];
    try {
        new (buffer2) T(original);

        // stuff here

    } catch(...) {
        reinterpret_cast<T*>(buffer2)->~T();
        throw;
    }

} catch(...) {
    reinterpret_cast<T*>(buffer1)->~T();
    throw;
}

に比べ:

char buffer1[sizeof(T)];
new (buffer1) T(original);
Defer const defer1{[&buffer1]() { reinterpret_cast<T*>(buffer1)->~T(); } };

char buffer2[sizeof(T)];
new (buffer2) T(original);
Defer const defer1{[&buffer2]() { reinterpret_cast<T*>(buffer2)->~T(); } };

// stuff here

それらを一般化することは良い考えだと思われることに注意してください:

class Guard {
public:
    explicit Guard(std::function<void()> f): _function(std::move(f)) {}

    Guard(Guard&&) = delete;
    Guard& operator=(Guard&&) = delete;

    Guard(Guard const&) = delete;
    Guard& operator=(Guard const&) = delete;

    ~Guard() {
        if (not _function) { return; }
        try { _function(); } catch(...) {}
    }

    void cancel() { _function = std::function<void()>{}; }

private:
    std::function<void()> _function;
}; // class Guard

class Defer {
public:
    explicit Defer(std::function<void()> f): _guard(std::move(f)) {}
private:
    Guard _guard;
}; // class Defer
于 2014-03-12T10:02:22.673 に答える