9

重複の可能性:
関数の try ブロックが役立つのはいつですか?
関数の try-catch 構文の違い

このコードは、 class 内でオブジェクトintを構築しているときに例外をスローします。例外は通常のブロックによってキャッチされ、コードは次のように出力します。DogUseResourcesinttry-catch

Cat()  
Dog()  
~Cat()  
Inside handler

#include <iostream>
using namespace std;

class Cat
{
    public:
    Cat() { cout << "Cat()" << endl; }
    ~Cat() { cout << "~Cat()" << endl; }
};

class Dog
{
    public:
    Dog() { cout << "Dog()" << endl; throw 1; }
    ~Dog() { cout << "~Dog()" << endl; }
};

class UseResources
{
    class Cat cat;
    class Dog dog;

    public:
    UseResources() : cat(), dog() { cout << "UseResources()" << endl; }
    ~UseResources() { cout << "~UseResources()" << endl; }
};

int main()
{
    try
    {
        UseResources ur;
    }
    catch( int )
    {
        cout << "Inside handler" << endl;
    }
}

UseResources()ここで、コンストラクターの定義を、function try block以下のように を使用するものに置き換えると、

UseResources() try : cat(), dog() { cout << "UseResources()" << endl; } catch(int) {}

出力は同じです

Cat()  
Dog()  
~Cat()  
Inside handler 

つまり、まったく同じ最終結果が得られます。

それでは、の目的は何function try blockですか?

4

2 に答える 2

12

UseResourcesが次のように定義されていると想像してください。

class UseResources
{
    class Cat *cat;
    class Dog dog;

    public:
    UseResources() : cat(new Cat), dog() { cout << "UseResources()" << endl; }
    ~UseResources() { delete cat; cat = NULL; cout << "~UseResources()" << endl; }
};

Dog::Dog()スローcatされると、メモリ リークが発生します。のコンストラクUseResourcesターが完了しなかったため、オブジェクトが完全に構築されませんでした。したがって、デストラクタは呼び出されません。

このリークを防ぐには、関数レベルの try/catch ブロックを使用する必要があります。

UseResources() try : cat(new Cat), dog() { cout << "UseResources()" << endl; } catch(...)
{
  delete cat;
  throw;
}

あなたの質問にもっと完全に答えるために、コンストラクターの関数レベルの try/catch ブロックの目的は、特にこの種のクリーンアップを行うことです。関数レベルの try/catch ブロックは例外を飲み込むことができません(通常のものはできます)。何かをキャッチすると、 で明示的に再スローしない限り、catch ブロックの最後に到達したときに再度スローされthrowます。あるタイプの例外を別のタイプに変換することはできますが、それを飲み込んで、それが起こらなかったように続けることはできません。

これは、クラス メンバーであってもネイキッド ポインターの代わりに値とスマート ポインターを使用する必要があるもう 1 つの理由です。あなたの場合のように、ポインターの代わりにメンバー値がある場合は、これを行う必要がないためです。この種のことを強制するのは、ネイキッド ポインター (または RAII オブジェクトで管理されていない他の形式のリソース) の使用です。

これが関数の try/catch ブロックの唯一の正当な使用法であることに注意してください。


関数の try ブロックを使用しない理由が増えました。上記のコードは微妙に壊れています。このことを考慮:

class Cat
{
  public:
  Cat() {throw "oops";}
};

UseResourcesでは、のコンストラクターではどうなるでしょうか。まあ、new Cat明らかに、式はスローされます。しかし、それはそれcatが初期化されなかったことを意味します。つまり、delete cat未定義の動作が発生します。

の代わりに複雑なラムダを使用して、これを修正しようとする場合がありますnew Cat

UseResources() try
  : cat([]() -> Cat* try{ return new Cat;}catch(...) {return nullptr;} }())
  , dog()
{ cout << "UseResources()" << endl; }
catch(...)
{
  delete cat;
  throw;
}

これは理論的には問題を解決しますが、仮定された の不変条件を破りUseResourcesます。つまり、UseResources::cat常に有効なポインターになります。それが実際に の不変式である場合、例外にもかかわらず のUseResources構築が許可されるため、このコードは失敗します。UseResources

基本的に、 (明示的または暗黙的に) そうnew Catでない限り、このコードを安全にする方法はありません。noexcept

対照的に、これは常に機能します。

class UseResources
{
    unique_ptr<Cat> cat;
    Dog dog;

    public:
    UseResources() : cat(new Cat), dog() { cout << "UseResources()" << endl; }
    ~UseResources() { cout << "~UseResources()" << endl; }
};

要するに、関数レベルの try ブロックを深刻なコードの匂いと見なしてください。

于 2011-12-02T16:50:24.460 に答える