例外を投げるコンストラクタをうまく処理するには?

私は時々、コンストラクタに投げられるオブジェクトの構築をどのように処理するのかと思っています。あなたはどうやってそれをするのだろう。

次のスニペットを考えてみましょう。 "サーバー"がTCPで受信するメッセージを表す TCPMessage という名前のクラスがあります。受信したメッセージが無効な場合(つまり、 TCPMessage のコンストラクタで計算されたCRC32がチェックアウトしない)、 TCPMessage のコンストラクタがスローされます。

それで私はそれをどうやって行うのですか?あなたはもっと良い方法を知っていますか?私は本当にあまりにもエレガントに見えないので、尋ねています。

void TCPConnection::handleRead(
  const boost::system::error_code& error,
  char* read_buffer
)
{
  if (!error) {
    TCPMessage* message = NULL;//being verbose here
    try {
      message = new TCPMessage(read_buffer);
    } catch (const char* e) {
      std::cerr << "Instatiating TCPMessage: " << e << std::endl;
    } catch (...) {
      std::cerr << "Instatiating TCPMessage: unknown exception." << std::endl;
    }

    if (message) {
     //if created succesfully
     //process the message and delete it
      SeekurJrRC::Core::Driver& driver = boost::asio::use_service(_io_service);
      driver.processMessage(*message);
      delete message;
    }
  }
  delete [] read_buffer;
}

ああ、私は char * read_buffer を使用し、別の関数でそれを削除するよりもよく知っていることを知っています。 shared_ptr のやり方です。

0
if(message)のような記述は絶対にしないでください。あなたが演算子bool を実装していない限り、まあです。条件は常に真であり、有用なものは何もチェックしないので、これは役に立たない。 演算子new がthrows、のいずれかでnull以外の値を返します。だからnull以外のものをチェックするだけでは何の役にも立たない。 if節にあるものを try {} に移動します。
追加された 著者 Damon,
@ヤフール:あなたはもちろん、私の悪いです。しかしそれはやや悪い構図です。成功している構築に依存するコードは、私の意見ではtry節の中にあるべきです(何もチェックせずに、例外が発生した場合、コンパイラは制御フローを破ります)。これは理にかなって、人生を楽にします。
追加された 著者 Damon,
@Damon:OPコードで動作します: new が例外をスローするとその例外は捕捉されますが、 message は決して割り当てられないので、 if(message)チェックをいくらか意味深くします。 (もちろん、これらのフープを飛び越える必要はないようにコードを再設計する必要がありますが、これは別の問題です)
追加された 著者 jalf,
char * を使用しないことがわかっている場合は、なぜやっていますか?これは、このコードで何が間違っているかの大きな部分であり、例外を処理するのに苦労するものです。あなたがよく知っているなら、する方が良いです。もしあなたが馬鹿げたコードを書いて「良いコードを書くにはどうすればいいですか?」と言います。同時に、「途中でもっと良いコードを書く方法を知っています」と言うと、私たちは何を期待していますか?
追加された 著者 jalf,
@ jalf;ただ、あなたが TCPMessage の部分に集中することを望みました。私はいくつかのコード(上記の一部です)をより賢明な形にリファクタリングしています。次に char *shared_ptr に変更するとリストに表示されます。私の状況を説明することを願っています。
追加された 著者 user312650,

5 答え

あなたの問題は例外ではなく、生のポインタとRAIIの欠如です。

あなたのコードを少し消毒する:

void TCPConnection::handleRead(
  const boost::system::error_code& error,
  char* read_buffer
)
{
  if (!error) {
    try {
      TCPMessage message(read_buffer);
      SeekurJrRC::Core::Driver& driver = boost::asio::use_service(_io_service);
      driver.processMessage(message);

    } catch (const char* e) {
      std::cerr << "Instatiating TCPMessage: " << e << std::endl;
    } catch (...) {
      std::cerr << "Instatiating TCPMessage: unknown exception." << std::endl;
    }
  }
}

new calls should be wrapped in RAII objects, not dangle around freely in your user code. delete calls should never be explicit, but instead be handled by the destructors defined in your RAII objects.

例外がスローされた場合、オブジェクトは自動的に破棄され、クリーンアップされ、過度に複雑な "try/catch/check-for-success"ダンスは必要ありません。例外がスローされなかった場合は、正常に続行されます。投げられた場合は、 try ブロックを離れ、オブジェクトは自動的に破棄されます。

ここでは、 try / catch ブロックを実際に必要する必要はありません。 catch を使用する唯一の方法は、エラーメッセージを出力することです。プログラムフローやリソースリークの防止には必要ありません。通常は、意味のあるところでエラーを処理します。恐らくそれはコールツリーのほんの少し上にあります。ここでは、失敗した読み込みについて何をすべきかを知っています。このレベルでは、エラーが発生したことを示すために例外をエスケープさせる方が理にかなっています。

void TCPConnection::handleRead(
  const boost::system::error_code& error,
  char* read_buffer
)
{
  if (!error) {
    TCPMessage message(read_buffer);
    SeekurJrRC::Core::Driver& driver = boost::asio::use_service(_io_service);
    driver.processMessage(message);
  }
}
5
追加された

通常の方法は、例外を解決できる時点で例外を処理することです。

たとえば、「接続が切断された」例外がスローされた場合は、ユーザーに何をすべきかを尋ねることができます。再接続するか、アプリケーションを閉じることができます。

その関数で例外を処理する必要はありません。私はあなたがエラーメッセージを印刷しているのを見る。それが唯一のことなら、それは大丈夫です。あなたが言ったように、それはスマートポインタを使用する方が良いでしょう。このようなもの :

void TCPConnection::handleRead(
  const boost::system::error_code& error,
  char* read_buffer
)
{
  if (!error) {
    try{
    std::auto_ptr< TCPMessage > message( new TCPMessage(read_buffer) );
    SeekurJrRC::Core::Driver& driver = boost::asio::use_service<< "Instatiating TCPMessage: " << e << std::endl;
    } catch (...) {
      std::cerr << "Instatiating TCPMessage: unknown exception." << std::endl;
    }

  }
  delete [] read_buffer;
}

const char * ではなく、いくつかのカスタム例外タイプをキャッチするほうがよいことに注意してください。

1
追加された
これは危険です。 delete 呼び出しを取り除き、値で例外をキャッチしないでください。
追加された 著者 curiousguy,

コンストラクターは、リソース割り当て関連のジョブだけを処理する必要があります。複雑な初期化が必要な場合は、専用のinitメンバ関数が必要です。それがATLのしくみです。このようにして、コンストラクタに例外を投げるのを避けることができます。

スマートポインタが問題を解決しない場合、コンストラクタで例外がスローされたときにメモリリークが発生する可能性があります。

    std::autor_ptr pWidget(new Widget()); 

上記の声明は、少なくとも次の3つのことを行います:

   1. call new operator to allocate memory
   2. construnct an object by calling its constructor 
   3. constuct the smart poniter. 

2番目のステップで例外がスローされた場合、スマートポインタは構築されず、決して破棄されません。その結果、最初のステップで割り当てられたメモリがリークします。

1
追加された
誰があなたにこのナンセンスを教えた?
追加された 著者 curiousguy,
ここに漏れはありません。
追加された 著者 curiousguy,
@curiousguy:これをデモンストレーションするためのシミュレーションを行いました。問題は、コメントに貼り付ける方法がわかりません。
追加された 著者 Bruce Adi,
以下の2つのリンクをご覧ください: codepad.org/IRzZR0Cx codepad.org/lLJIyKwE
追加された 著者 Bruce Adi,

どこか例外を処理する必要があります。 新しいTCPMessage(read_buffer)を実行する場所でコードを乱雑にしたくない場合は、コンストラクタの特殊構文を使用して例外処理を1か所で行うことができます。例えば

class TCPMessage {
...
public:
  TCPMessage (const char *read_buffer)
  try {
    ...
  }
  catch(const char *p){ ... }
  catch(...) { ... }
...
};
0
追加された

try-catch-blockで処理を囲むのは何が問題になりますか?

void TCPConnection::handleRead(
  const boost::system::error_code& error,
  char* read_buffer
)
{
  if (!error) {
     try {
         std::auto_ptr message(new TCPMessage(read_buffer));

        //if created succesfully
        //process the message and delete it
         SeekurJrRC::Core::Driver& driver = boost::asio::use_service(_io_service);
         driver.processMessage(*message);
     } catch (const char* e) {
         std::cerr << "Instatiating TCPMessage: " << e << std::endl;
     } catch (...) {
         std::cerr << "Instatiating TCPMessage: unknown exception." << std::endl;
     }
  }
  delete [] read_buffer;
}

もちろん、コードの別の部分が投げることができるとき、例外安全のために適切なスマートポインタを使うべきです。この関数のすべての例外を正しく処理できない場合もあります。

0
追加された
operator <<がスローするとどうなりますか?とにかく、例外は再投げられるべきです。そして、 delete を削除する必要があります。
追加された 著者 curiousguy,