2011-12-02 12 views
8

可能性の重複:クラスUseResources内部Dogオブジェクトを構築しながら
When is a function try block useful?
Difference between try-catch syntax for function関数tryブロックの目的は何ですか?

このコードは、int例外をスロー。 int例外は、通常のtry-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) {} 

出力は

0123と同じです
Cat() 
Dog() 
~Cat() 
Inside handler 

つまり、まったく同じ最終結果です。

それでは、function try blockの目的は何ですか? Dog::Dog()スロー

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

場合、catは、メモリリークが発生します:UseResourcesは、次のように定義されている場合

+4

複製物の複製の複製... – Xeo

+0

これはC++ 11ですか?私は前にそれを見たことがない –

+0

@VJovicこれがいつ言語で導入されたのか分かりません。しかしそれは新しいものではない。 – Belloc

答えて

9

を想像してみてください。 UseResourcesのコンストラクタが決してで完了した場合というオブジェクトが完全に構築されたことはありません。したがって、そのデストラクタは呼び出されません。 、コンストラクタ内の関数レベルのtry/catchブロックの目的は、具体的でより完全にあなたの質問に答えるために

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

このリークを回避するには、関数レベルのtry/catchブロックを使用する必要があります。この種のクリーンアップを行う。ファンクションレベルtry/catchブロックできません例外をスワップできます(通常のものが可能です)。もし彼らが何かを捕まえれば、あなたはthrowで明示的にそれをやり直さない限り、キャッチブロックの終わりに達すると再び捨てるでしょう。あるタイプの例外を別のタイプに変換することはできますが、それを飲み込むことはできません。

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

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


機能tryブロックを使用しない理由がさらにあります。上記のコードは微妙に壊れています。UseResourcesのコンストラクタで何が起こるか、そう

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

:これを考えてみましょうか?さて、式new Catは明らかにスローされます。しかし、それはcatが決して初期化されていないことを意味します。つまり、delete catは未定義の動作をもたらします。理論的には問題を修正

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

を、それがUseResourcesの仮定不変を破る:

あなたはだけでなくnew Catの複雑なラムダを使用することによって、これを修正しようとするかもしれません。つまり、UseResources::catは常に有効なポインタになります。これが実際にUseResourcesの不変量である場合、このコードは例外としてUseResourcesの構築を許可するので失敗します。

new Catnoexcept(明示的または暗黙的)でない限り、基本的にこのコードを安全にする方法はありません。 深刻コードのにおいとしての機能レベルのtryブロックに見て、要するに

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

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

は対照的に、これは常に動作します。

+0

面白いです。これまで関数のtryブロックで見たこの例では、このクリーンアップの問題が現れました。しかし、あなたの例では、プログラムはとにかに中止されるので、この機能の大きな点は何ですか?とにかくOSがそれに行くなら、クリーンアップの目的は何ですか? – Belloc

+0

例外処理は失敗を処理することではなく、より正常に処理することです。この機能は、リソースを正常に解放するのに役立ちます。実用的な例としては、クライアントがクラッシュし、これを使用してサーバーリソースを解放し、他のクライアントには使用できない状態にならない場合があります。 – AJG85

+0

@ AJG85しかし、 'terminate()'は、関数tryブロックでは常に呼び出されます。この場合、すべてのリソースはOSによって解放されます。それは本当ですか? – Belloc

2

通常の関数tryブロックは比較的目的がありません。彼らは、体内のtryブロックとほぼ同じだ:

int f1() try { 
    // body 
} catch (Exc const & e) { 
    return -1; 
} 

int f2() { 
    try { 
    // body 
    } catch (Exc const & e) { 
    return -1; 
    } 
} 

唯一の違いは、少し大きめの関数スコープで機能してみてください - ブロックの生活、第二の構造は、関数本体に住んでいながら、 -scope - 前のスコープは関数の引数だけを参照し、後者もローカル変数を参照します(ただし、tryブロックの2つのバージョンには影響しません)。

のみ興味深いアプリケーションはコンストラクタ -tryブロックで来る:

Foo() try : a(1,2), b(), c(true) { /* ... */ } catch(...) { } 

これは、初期化子の1からの例外をキャッチすることができます唯一の方法です。 を処理することはできません。オブジェクト構造全体がまだ失敗している必要があるためです(したがって、必要に応じて例外を指定してcatchブロックを終了する必要があります)。ただし、で、イニシャライザのリストから例外を処理する唯一の方法です。

これは便利ですか?おそらくそうではありません。 コンストラクタtryブロックとひどいそのものである以下の、より一般的な「アサインにヌル初期化-と-」パターン、間に違い基本的にありません:

Foo() : p1(NULL), p2(NULL), p3(NULL) { 
    p1 = new Bar; 
    try { 
    p2 = new Zip; 
    try { 
     p3 = new Gulp; 
    } catch (...) { 
     delete p2; 
     throw; 
    } 
    } catch(...) { 
    delete p1; 
    throw; 
    } 
} 

は、あなたが見ることができるように、あなたはunmaintainableを持っています、スケーラブルな混乱。 constructor-try-blockは、さらに多くのポインタがすでに割り当てられているかどうかを知ることさえできないため、さらに悪化します。したがって実際にの場合は、正確にの2つの漏れ可能な割り当てがある場合に便利です。更新:this questionという読者のおかげで、実際には、メンバーオブジェクトを参照することは未定義の動作であるため、キャッチブロックを使用してリソースをクリーンアップすることはできません()ことに警告されました。 So [end update]

要約:これは役に立たない。

+0

「役に立たない」もちろん、例外を翻訳する必要がある場合を除きます。 –

関連する問題