関数トライブロックの目的は何ですか? [複製
-
27-10-2019 - |
質問
考えられる複製:
関数Tryブロックはいつ役立ちますか?
機能のトライキャッチ構文の違い
このコードはスローします int
構築中の例外 Dog
クラス内のオブジェクト UseResources
. 。 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()
コンストラクター、aを使用するもの function try block
, 、 以下のように、
UseResources() try : cat(), dog() { cout << "UseResources()" << endl; } catch(int) {}
出力は同じです
Cat()
Dog()
~Cat()
Inside handler
IE、まったく同じ最終結果。
そのとき、aの目的 function try block
?
解決
もし想像してみてください 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
'のコンストラクターは決してありません 完了しました, 、オブジェクトが完全に構築されることはありませんでした。したがって、デストラクタと呼ばれていません。
このリークを防ぐには、関数レベルのトライ/キャッチブロックを使用する必要があります。
UseResources() try : cat(new Cat), dog() { cout << "UseResources()" << endl; } catch(...)
{
delete cat;
throw;
}
あなたの質問にもっと完全に答えるために、コンストラクターの関数レベルのトライ/キャッチブロックの目的は、特にこの種のクリーンアップを行うことです。関数レベルのトライ/キャッチブロック できません 嚥下例外(通常のものはできます)。彼らが何かを捕まえた場合、彼らはあなたがそれを明示的に蘇らせない限り、彼らがキャッチブロックの終わりに達したときに再びそれを投げます throw
. 。あるタイプの例外を別のタイプに変換することはできますが、それを飲み込むだけで、起こらなかったように続けることはできません。
これは、クラスのメンバーであっても、裸のポインターの代わりに価値とスマートポインターを使用する必要があるもう1つの理由です。あなたの場合のように、ポインターの代わりにメンバーの値を持っているだけなら、これを行う必要はないからです。この種のことを強制するのは、裸のポインター(またはRAIIオブジェクトで管理されていない他の形式のリソース)の使用です。
これは、関数Try/Catchブロックの唯一の正当な使用であることに注意してください。
関数を使用しない理由は、ブロックを試してください。上記のコードは微妙に壊れています。このことを考慮:
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ブロックには、目的が比較的少ない。それらは、体内のトライブロックとほぼ同じです:
int f1() try {
// body
} catch (Exc const & e) {
return -1;
}
int f2() {
try {
// body
} catch (Exc const & e) {
return -1;
}
}
唯一の違いは、関数 - トリブロックがわずかに大きな関数スコープに存在し、2番目の構造は関数球スコープに住んでいることです。前者のスコープは関数引数のみを見て、後者はローカル変数も見ます(ただしこれは、Tryブロックの2つのバージョンに影響しません)。
唯一の興味深いアプリケーションは、にあります コンストラクタ-Try-Block:
Foo() try : a(1,2), b(), c(true) { /* ... */ } catch(...) { }
これは、初期化装置の1つからの例外をキャッチできる唯一の方法です。それはいけません 扱う 例外は、オブジェクト全体の構築がまだ故障する必要があるためです(したがって、必要であろうとなかろうと、例外を除いてキャッチブロックを終了する必要があります)。しかし、それ は 具体的に初期化リストから例外を処理する唯一の方法。
これは便利ですか?おそらくそうではありません。 コンストラクターのトライブロックと次のより典型的な「初期化から控えめな割り当て」パターンの間に本質的に違いはありません。それ自体がひどいです。
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;
}
}
ご覧のとおり、あなたは維持不可能で無防備な混乱を持っています。コンストラクター - トライブロックは、すでに割り当てられているポインターの数を伝えることさえできなかったため、さらに悪化します。本当にそうです それだけ 正確に持っている場合は便利です 2 リーク可能な割り当て。 アップデート: 読書のおかげです この質問 私は実際にその事実に警告されました キャッチブロックを使用してリソースをクリーンアップすることはできません メンバーオブジェクトを参照することは未定義の動作です。だから[更新を終了
要するに、それは役に立たない。