web-dev-qa-db-ja.com

例外をスローせずに、コンストラクタがboolを返してその成功または失敗を示しないのはなぜですか?

C++コンストラクターを例にとると、それらは値を返さないことがわかります。なぜ最初にBjarne Stroustrupがnotを決定して、コンストラクタが 'false'を返して失敗したことを示し、ランタイムシステムが例外をスローするように構築されたメンバーを破壊できるようにしたのですか? OO言語の設計者がそうすることを決定するしないようにする懸念は何ですか? "new"演算子は、したがって、もしそれが見た場合、 "nullptr"を返すことができますコンストラクターはfalseを返します(.data/.bss領域または.stack内の)静的オブジェクトの場合、コンパイラーが生成した構築コードは引き続き検出し、それに応じて中止または終了します。

例として動的割り当てを使用して、次の2つのコーディングパラダイムを検討してください。

objp = new object; // constructor returns 'false', 'new' returns 'nullptr'.
if (objp != nullptr) {
    do_something(objp);
} else {
    error_handling();
}

次と比較:

try {
    objp = new object; // object throw exception when construction failed
    do_something(objp);
} catch (const typedException &e) {
    error_handling();
}

構築の失敗の理由をエンコードする必要がある場合、構築が失敗すると 'objp'を使用して情報を渡すことができないので、後者を使用した例外はもちろんより役立ちます。しかし、理由が単純な場合(たとえば、「メモリ不足」)、またはエラー処理をスキップしても害がない場合(do_something()が装飾を行うだけかもしれません)、そのような単純なケースで例外処理を実行する必要がありますか? C++に両方のパラダイムが存在することを許可するのはどうですか?

(別の例は、小さな組み込みC++コンパイラーが例外をサポートしていない場合でも、この方法で「構築失敗」処理をサポートできます。)

まあ、私の比較は誤解を招くかもしれません、私は構造的例外処理に反対ではありません、逆に、私は[〜#〜] love [〜#〜]特に大きなシステムの例外です。しかし、コードとデータ領域の両方が不足している小さな組み込みシステムに関しては、私は2度考えます。

例外処理フレームはsetjmp()やlongjmp()と同様のもので、実行時間とメモリ使用量の両方で非常に負荷が高くなります。一方、(objp == nullptr)比較は、比較とジャンプのみを受け取ります。言うまでもなく、一部のコンパイラは例外処理もサポートしていません。これらの場合、構築失敗は他の方法でしか処理できません。それは昔、ターボパスカル5.5 OOPが構築失敗時に「失敗」を呼び出すことができ、新しく割り当てられたオブジェクトがnullになることを思い出させます。

他の言語はどうですか?すべてのOOP言語は、構築失敗の場合に例外を使用しますか?

実際、私がC++を学んだ時点では、例外処理はまったくありません(Turbo C++ 3.0/Borland C++ 3.1)。その前に、動的構築の「失敗」をサポートするTurbo Pascal 5.5が失敗したことを学びました。しかし、そのときにC++に移行すると、実際にinitを実行するInit()関数を定義せずに構築の失敗をテストする方法がないため、これは少し動揺します。それ以来、なぜコンストラクタが値を返せないのかと疑問に思いました。

現在、コンストラクタから値を返すと、言語が「矛盾する」ようになると思います。コンストラクターから値を返し、ランタイムシステムがそれをテストする場合、この種類の動作は、コードでテストできるため、通常の関数呼び出しとは大きく異なります。プログラミング言語を設計するとき、この種の不整合は良い考えではないでしょうか?どう思いますか?


C++の初期の頃は例外処理はありませんでしたが、Bjarne Stroustrupは依然としてコンストラクターにエラー条件を返させませんでした。同じことが他のOO言語をその時にしました(私が間違っていれば私を訂正してください)。したがって、例外処理を使用することは本来の意図ではありませんでしたコンストラクタの処理に失敗しました。なぜですか?答えが見つかったと思います。参照してください 自分の答えはこちら 。ありがとう。

6
user249859

あなたの質問は、例外メカニズムの価値についての根本的な誤解を示しています。問題の言語に例外がない場合は、もちろん、エラーコードが唯一の方法です。しかし、例外をサポートする言語でエラーコードを使用することは、多くの場合正しい答えではありません。

どうして?

結局のところ、非常に多くの場合、ショーの停止の問題の原因に本当に近いコードは、それが発生したときに何をすべきかわかりません。

エラーに近いコードは何をすべきかわからないことが多いので、そのコードがエラーを処理するために他の誰かにそれを行わせる以外に何もすることはありません。

エラーコードが返されると、すべての呼び出し元は通常、エラーをチェックする必要があります。

例外処理を入力してください

例外処理は、問題のある呼び出し先を、通常はエラーのソースからかなり削除されている真に有効なエラーハンドラから分離できるようにするメカニズムです。

優れたハンドラーは、単にエラーをログに記録してエラーを呼び出しチェーンに伝達するだけでなく、実行中のより広範な計算を中止または再試行する方法を知っています。優れたハンドラーの兆候は、エラーを処理したため、呼び出しチェーンにエラーを伝播する必要がないことです。

例外処理では、通常、個々のメソッド呼び出しを例外処理で囲みません。これには、コンストラクターと新しい操作が含まれます。

例外処理を使用すると、例外ハンドラーをより適切に定義でき、例外のソースからかなり離れています。これは、通常、エラーが発生した場合の対処方法をよりよく理解できるため、利点です。

(例外処理では構造化エラーも許可されますが、別の議論のために残しておきます。)


まだ確信が持てない場合は、これらのコードシーケンスを比較してください。

objp = new object (); // constructor returns 'false', 'new' returns 'nullptr'.
if (objp != nullptr) {
    do_something(objp);
} else {
    error_handling();
}

と:

objp = new object (); // object throw exception when construction failed

後者は、例外を使用して、はるかに明確であり、議論の余地のある大多数のユースケースに適しています。

14
Erik Eidt

あなたは確かにcouldこのように機能する言語を定義しますが、最終的にはC++とはまったく異なる言語になるでしょう。

たとえば、C++では、コンストラクターを呼び出して、式を評価するプロセスで一時オブジェクトを作成できます。

_MyInteger a = 2;
MyInteger b = 3;
MyInteger c;
c = a + b;
_

これを行うと、_a + b_は一時オブジェクト(おそらくMyInteger型)を作成します。次に、そのオブジェクトはcに割り当てられます。

ただし、コンストラクターがブール結果を返した場合、これはあまりうまくいきません。 _a+b_は(どうやら)trueまたはfalseになります。これらはそれぞれ_1_および_0_に変換されるため、2 + 3の結果は0または1になります(同様に、他のすべての計算では0または1の結果が得られます)。

もちろん、この問題を解消する方法はいくつかあります。たとえば、オブジェクトの割り当てを禁止するだけです。この制限があれば、前述のように書くことができます。

_MyInteger a{2};
MyInteger b{3};
MyInteger c{a};
c.add(b);
_

2つの数値のみを追加している限り、これは大きな問題ではありません。しかし、もっと複雑な表現では、結果は不自然でほとんど耐えられないほど冗長になるように思えます。プログラマーは、入力オペランドの1つから初期化されたすべての一時オブジェクトを明示的に作成し、他のオペランドに基づいて変更することを強いられます。たとえば、a = (b+c)*(d/e);は次のようになります。

_MyInteger temp1{b};
temp1.add(c);
MyInteger temp2{d};
temp2.div(e);
temp1.mul(temp2);
MyInteger a{temp1};
_

そのようなことは確かに可能です。実際、何年にもわたって(同様の構文を使用して)同様の機能を提供してきた言語があります。

_ mov ebx, b
 add ebx, c
 mov eax, d
 xor edx, edx
 mov ecx, e
 div ecx
 mul eax, ebx
_

C++の目的は、より高いレベルの抽象化に移行することでした。あなたが発明したこの言語は逆方向に進み、アセンブリ言語を再発明しますが、より冗長な構文を使用します。

概要

あなたが主張していることできるはず確かに行われます。設計と実装はかなり簡単です。結果はとても不器用で、誰もがそれを使用することを想像するのは難しいです。

4
Jerry Coffin

両方のアプローチ(例外またはエラーコード)は、これらの言語で既にサポートされています。エラーコードアプローチを使用するには、例外をスローしたり、スローする可能性のあるものを呼び出したりしないでください。純粋な値をフィールドにコピーする以外は何もしないコンストラクタを記述します。失敗する可能性のある操作を別のメソッドに移動します。

悪い時代には、Microsoftはこのアプローチを実際に推奨していました(おそらく、初期のC++コンパイラが例外スタックの巻き戻しをまったく実装していなかったため)。

これに関する課題は、「純粋なエラーコードアプローチ」は多くの場合不可能であるということです。なぜなら、コードのいずれかで行うほとんどすべてのことは、スローする可能性があるためです。たとえば、文字列に対するほとんどすべての操作を含む、メモリを割り当てるもの。

したがって、実際の選択は次のとおりです。

  • 例外を処理し、返されたエラーコードをチェックする
  • 例外を扱うだけ

とにかく例外に対処する必要がある場合は、例外を一貫して使用することもできます。

リソースの制約に関する議論は、一部のコミュニティによって真剣に受け止められています。非常に制約された組み込み環境。しかし、それは小さなニッチです。以前は電話がリソースの制約を受けていましたが、現在、ほとんどの電話ソフトウェアは、GCや例外などを使用してJavaベースです。

2

例外処理フレームはsetjmp()やlongjmp()と同様のもので、実行時間とメモリ使用量の両方で非常にコストがかかります。 (objp == nullptr)比較は比較とジャンプのみを受け取ります

それらは、成功例を含むすべてのケースで比較とジャンプです。 EHは、例外がスローされないことによるコストがゼロになる可能性があるため、通常の成功の場合により良い時間を提供できます。

Erikが適切に指摘したように、例外の全体的なポイントは、あなたがしないでくださいどこでも例外をチェックすることです。

0
DeadMG

Objective-Cでは(一種の)発生します。新しく作成されたオブジェクトは常にヒープ上にあるため、initメソッド(C++のコンストラクターのようなもの)はオブジェクトへの参照を返します。 initメソッドが失敗した場合、nilを返します。

Initメソッドに「nonnull」として注釈を付けることができます。これは、nilを返すことが許可されていないことを意味します。その場合、正しいパラメーターが指定されている場合はinitメソッドが失敗することはなく、誤ったパラメーターはプログラマーエラーであると想定されるため、initメソッドがnilを返したい場合は、プログラミングエラーとなり、例外が発生します。 -Objective Cでは、ほとんどの場合、例外によってプログラムが強制終了されます(例外はプログラミングエラーのために発生するため、めったに試行/キャッチしません)。

もちろん、nilを返す、またはnilを返さないことは、ブール値のパラメーターと同じです。これは、C++のファクトリメソッドでも実行できることです。それらを呼び出すと、新しく作成されたオブジェクトへのポインターを取得するか、nullポインターを取得します。

0
gnasher729

言語を変更することなく、これをすでに行うことができます。

Bool&出力パラメーターを各コンストラクターに追加し、例外をスローする代わりに設定します。

Nothrow newでヒープオブジェクトを割り当てることができます

しかし、正直に言うと、この時点でcと書いている可能性があります。

0
Bwmat