質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

5回答

2959閲覧

例外か戻り値かの判断

BeatStar

総合スコア4958

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

0クリップ

投稿2017/08/17 05:01

しょーもない質問だと思いますが。

C++で趣味でやっています。

他の方の 別の言語 ( Pythonだったかな? ) の質問 ( 確か エラー処理に関しての質問だった気が... ) に回答したときに

別の方 ( 質問者さん以外 ) から「それはレガシーな方法ですね。モダンなやり方は"例外を投げること"です...」 というようなご指摘を受けました。

( どのページだったか忘れましたが... )

何らかのエラーのとき例外を投げるっていうのはわかります。

ですが、その例外の影響力が微々たるものである場合はどうなんでしょうか?

例えば C++ & Windows API で GUI ( -mwindows でやる方 ) を作成するとします。

CreateWindowInstance関数なるもの ( 自作関数 ) で メインウィンドウ用のクラスオブジェクト ( 自作クラスから生成。 ) を生成して返すとします。

メモリ不足とかなら「処理続行 不可能」なので

  1. iniファイルからデータを読み込む -> 構造体 WININFO (自作) に格納
  2. WININFO をもとに インスタンス化
  3. 生成したオブジェクトを返す

という流れだとします。

iniファイルがないとか、存在はするけど 指定のセクションやキーがないとかで取得失敗した場合はどうすればいいでしょうか?

例外を投げるのはいいですが、

大雑把な CreateWindowInstanceを書くと ( WMainWindow をメインウィンドウのクラスとして。CExceptionEx は std::exceptionを継承した例外クラスだとします。 )


C++

1WININFO CreateWindowInfo( void ) throw(CExceptionEx){ 2 // ここでiniファイルを読み込んでWININFOを返す。もし存在しないなら例外を投げる。 3} 4 5 6WMainWindow* CreateWindowInstance( void ){ 7 WMainWindow* MainWindow; 8 9 try{ 10 11 WININFO wi; 12 13 wi = CreateWindowInfo(); // ここで例外が投げられる可能性あり 14 15 // ここで WININFO wi をもとに WMainWindow を構築する 16 17 // それ以外の何らかの処理? 18 19 }catch( CExceptionEx &e ){ 20 ... 21 }catch( 例外2 ){ 22 ... 23 } 24return MainWindow; 25}

となるとします。 (あくまでイメージです。)

関数, メンバ関数 ( メソッドとも。 ) の戻り値は 処理結果に集中すべきで、完了した・失敗した等は例外を投げることで明示すべきである

というのは理解できます。

しかし、上記の例の場合、もし iniファイルがないなら例外が投げられて catch以降に行くので "// ここでWININFO wi をもとに..." の部分以降は無視されますよね。

つまり iniファイルがある前提です。確かにiniファイルそのものはそこまで重くないので用意しておけばいいかもしれませんが、

これを任意にすることはできないのでしょうか?

例えば

iniファイルがない等で取得できなかった場合 -> 実行ファイルが指定するデータで行う
〃 できた場合 -> そのデータで行う

例:
main.ini から 背景色が取得できなかった場合は 背景色を "黒" とする
取得できた場合はその色とする

という感じにしたいのですが。

もし上記のソースコード風のやつだと iniファイルがない ( または セクションがない or キーがない ) ためにデータを取得できないなら生成不可。

となってしまいます。

一応 goto を使うことを思いつきましたが、goto は使いすぎるとスパゲッティになるので非推奨ですよね...

こういう任意のやつも例外として投げないといけないのでしょうか?

それとも「続行不可」な場合だけでしょうか?

くだらないかもしれませんがお願いします。

[情報]
言語: C++ ( Better C というやつ )

宜しくお願い致します。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答5

0

ベストアンサー

こんにちは。

例外は何が便利かというと、下記の2点です。

  1. 多段階で呼ばれた奥深い関数(サブルーチン)から途中の処理をすっ飛ばしてcatch文まで戻ってこれること。
  2. その際に適切にプログラムしていれば、途中で確保していたリソースも適切に解放されること。

ただし、問題点として実際に例外を投げると処理時間がかなり掛かることがあります。ループの中で例外を投げつつ繰り返し処理するような構造は、応答性能が悲惨なことになりがちです。

ですので、基本的にはそれまで進めていた処理を「中断」するようなエラーを検出した時は例外を使い、代替値を用いるなどして処理を「継続」できるようなエラーを検出した時は例外は使わず、必要に応じて戻り値等で伝達することが望ましいと思います。

第29回目 異常系プログラミングを簡単化する「例外」とその次の回で例外について、結構詳しく解説してます。もし、よかったら見てみて下さい。

投稿2017/08/17 05:21

編集2017/08/17 05:25
Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

(他の人も書いているのと同様に)例外を投げるのは、「エラーが起きたときにその時点で処理を中断し、呼び出し元(もしくはその関数自身のcatchブロック)にそのエラーを伝えたい」という状況だけであるべきです。

何をエラーとして扱うかについては、"契約による設計"という考え方が参考になります。ある関数の"事後条件"というものを定義し、関数の実行時にそれが満たせないときに例外が投げられうることになります。(ただし必ず例外にする必要はなく、この記事で紹介されているような仕組みを用いて、戻り値でエラーの情報を伝えられるならば戻り値を使用しても問題ありません。ここで例外を利用するか戻り値を使用するかどうかは、Chironianさんの回答にあるような利点/欠点も考慮しながら決めることになります)

(契約による設計の)参考: http://qiita.com/Kokudori/items/2e4bd32abf7abea3186f

今回の質問者さんの要求に合わせるならば、CreateWindowInfo()関数の事後条件は「iniファイルが無いとき/変なデータが含まれているときにも、適当なデフォルト値を使用して、必ず有効なWININFOを生成して返すこと」のように定義されることになると思います。その上でその定義に従った実装を行えば、「iniファイルが無いとき/変なデータが含まれているとき」にCreateWindowInfo()を呼び出しても事後条件が満たされるので、CreateWindowInfo()関数は例外を投げる必要はありません。

このように、事後条件というものを考えて、例外を投げるかどうかを判断できます。

以下は蛇足ですが、例外を使用することに関する注意などについてです


例えばですが、CreateWindowInfo()関数の事後条件を「正しくiniファイルが読み込めればそれを返す。(それ以外はエラーとして関数の失敗となる)」のように定義すると、「iniファイルが無いとき/ひとつでも変なデータが含まれているとき」のような状況が起きたときはエラー扱いになるため、CreateWindowInfo()関数から例外を投げることは妥当になります。

その場合は、そのエラーに対処する責務はCreateWindowInstance()関数側が負うようになり、それにあわせてCreateWindowInstance()関数の仕様は、「CreateWindowInfo()関数を呼び出してWININFOを設定する。ただし、CreateWindowInfo()関数が失敗したときには、デフォルト値を設定したWININFOを使用する」のようになってくるはずです。

その時のコードはおそらく次のようになります。

cpp

1WININFO CreateWindowInfo( void ) { 2 // ここでiniファイルを読み込んでWININFOを返す。 3 // iniファイルが無いとき/ひとつでも変なデータが含まれているときには例外を投げる。 4} 5 6WMainWindow* CreateWindowInstance( void ){ 7 WMainWindow* MainWindow; 8 WININFO wi = GetDefaultWinInfo(); // 何らかの初期値を設定 9 10 try{ 11 wi = CreateWindowInfo(); // ここで例外が投げられる可能性あり 12 }catch( CExceptionEx &e ){ 13 // エラーハンドリング 14 }catch( 例外2 ){ 15 ... 16 } 17 18 // ここで WININFO wi をもとに WMainWindow を構築する 19 return MainWindow; 20}

このように関数の事後条件の定義によって、誰が例外を発生させ誰がそれをcatchして対処するが変わってきますので、関数を適切に仕様化することは重要です。

ところで、例外を送出すると、それに対応するcatchブロックが見つかるまで関数の呼び出し階層を遡って処理が脱出していきます。そのため、ナイーブな設計のプログラムでは、例外の送出によってメモリリークやプログラムの不整合が発生することがあります。

もし例外を使用するのであれば、そのような問題を避けるために、必ず"例外安全性"というものを心がけるようにする必要があります。

参考: http://qiita.com/Kokudori/items/987073d59529b6c9a37c
参考: https://msdn.microsoft.com/ja-jp/library/hh279653.aspx

前者のリンク先の参考文献も非常に参考になります。


もう一つ余談ですが、質問者さんのコードの別の問題として、CreateWindowInfo()関数の定義に

cpp

1throw(CExceptionEx);

という"例外指定"という仕組みが使われている点があります。

この機能は主要なコンパイラで正しく実装されていない仕組みだったらしく、C++11とよばれるバージョンのC++の規格からは非推奨扱いになりました。

参考: https://cpplover.blogspot.jp/2014/10/c.html

なので、たとえCreateWindowInfo()関数からCExceptionExという例外を投げるとしても、関数の定義は

cpp

1WININFO CreateWindowInfo( void );

であるべきです。

投稿2017/08/25 03:27

編集2017/08/25 03:29
hotwatermorning

総合スコア15

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

例外か戻り値かのどちらが良いかと言われた場合、処理内容とロジックによるとしか言いようがないと思います。
例外が適切な場合もあれば、戻り値のほうがいい場合もあります。

一般的な話をすれば、処理中のどこで何のエラーが起こるか不明な場合は、例外で処理し、明らかに戻り値でエラーを判別できる場合は戻り値(あるいは引数にエラーコードで返す)が多いでしょうか。
ただ、例外の場合は、大域脱出してくるので、後処理に困る場合があります。

なので、両方のインターフェースを備えているライブラリもあります。(例えば、boost::filesystemはファイルアクセス時のエラーを例外で飛ばすか、エラーコードとして返すかを選択できます)。
エラー処理として2つ用意しているのは、処理ロジックによって使い分けたい場合が多いからでしょう。

投稿2017/08/17 08:28

PineMatsu

総合スコア3579

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

それ、突っ込んだの私ですね。この質問です。

BeatStar様の回答(部分)

C++とかなら bool にあたるもの ( C言語だと存在しないので int で代用とか ) を返して、
この戻り値が true ( true相当の値 ) のとき、次のメソッド実行っていう感じにしてはどうでしょうか?
私はC/C++でやっているので C++風に書くと

C++

1if( funcA() ){ 2 if( funcB() ){ 3 if( funcC() ){ 4 return true; 5 } 6 } 7}else{ 8 return false; 9}
**私のコメント** > これはバッドコードじゃないでしょうか? > 関数の成功/失敗を返り値のboolで返すのはいわゆるレガシーコードかと思います。 > C++にもPythonにも例外処理があります。 > また、戻り値を順に判定するような処理を書くにしても、ネストではなく > return funcA() && funcB() && funcC(); で充分なのでは? なお、当該の質問には私は回答していません。質問の趣旨がわからなかったためです。 上記の二重三重にネストされたコードを見て、思わず突っ込んだ次第です。 Chironian様が回答されているように、処理が継続するのであれば返り値を用いても良いと思います。 ただ、このコードは『中断する』類のエラーだと思ったのです。 --- **goto文について** [こちらのエッセイ](http://local.joelonsoftware.com/wiki/%E9%96%93%E9%81%95%E3%81%A3%E3%81%9F%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AF%E9%96%93%E9%81%95%E3%81%A3%E3%81%A6%E8%A6%8B%E3%81%88%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B)に、興味深い記述があります。(かなり最後の方です) > 例外は実質的に見えないgotoであり、目に見えるgotoよりいっそう悪い 例外を用いることで見通しが悪くなるのであれば、避けることも検討すべきかと。

投稿2017/08/17 07:21

LouiS0616

総合スコア35658

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

iniファイルがないとか、存在はするけど 指定のセクションやキーがないとかで取得失敗した場合はどうすればいいでしょうか?

まず、前提として「iniファイルが存在しないことが異常事態かどうか」を考えます。ご質問ではiniファイルがなければ規定の情報でウィンドウを作成するとのことなので、異常事態ではないと考えます。したがって、私ならCreateWindowInfo関数ではiniファイルが存在しなかったら例外を投げるのではなく、規定値に設定したWININFOオブジェクトを返すようにします。

では、iniファイルにあるべき情報がなかった場合をどうするかですが、いくつか考えられます。

  1. 読めない情報がある=異常事態と考えてエラーにする
  2. iniファイルの情報は破棄して規定値を使う
  3. 読めなかった情報を規定値で補う

1の場合はエラーを回復するにはiniファイルを修正するなり削除するなりしないといけないので、私なら採用しません。2や3はエラー扱いにはしないので、当然のことながら例外は投げません。
ということで、仕様をよく整理すれば、CreateWindowInfo関数は例外を投げる必要がないということになります。

投稿2017/08/17 07:15

catsforepaw

総合スコア5938

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

BeatStar

2017/08/25 05:36

ご回答ありがとうございます。 やはり状況しだいですか。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問