it-swarm-ja.com

C ++でのRustスタイルのエラー処理

私はRustがResult<T, E>タイプを使用してエラー処理を行う方法についていくつかの記事を読んでおり、私にとっては両方の世界のハイブリッドのベスト(例外と戻りコード)のように見えます)非常に役立つソリューションです。特定のエラーが例外的でスローする必要があるかどうか、または「予期された問題が発生したことを示すものにすぎないかどうか」を事前に本当に知らないAPIには特に便利だと思います'方法。Resultを返すと、その決定をあまり煩わしくない方法でAPIのユーザーに移動できます。そのため、これをC++プロジェクトに導入するかどうかについて議論しています。結果クラスを実装できますおおよそこのように:

template< class T, class E = std::exception >
class result
{
public:
  //constructors etc

  operator bool() const
  {
    return !!res;
  }

  T& operator * ( )
  {
    if( !res )
    {
      assert( ex );
      throw *ex;
    }
    return *res;
  }

  const E& exception() const
  {
    assert( ex );
    return *ex;
  }

private:
  std::optional< T > res;
  std::optional< E > ex;
};

だから関数があるとき

result< int > Read();

呼び出し元はいずれかの「エラーコード」スタイルを使用できます。

const auto result = Read();
if( !result )
{
  log.debug() << "failed read" << result.exception();
  return false; //or again return a Result
}
doSomething( *result );

または「例外」スタイル:

try
{
  doSomething( *Read() );
}
catch( const std::Exception& e )
{
  //do what's needed
}

または、Readからの例外を処理する方法がわからないと判断し、呼び出し元にそれを処理させる

doSomething( *Read() ); //throws if !result

これは一般的に良いアイデアのように思えますか、そして既存のC++コードで使用されている同様の原則はありますか?それとも、現時点では見られない深刻な欠点はありますか?オプションが多すぎるため、またはC++で使用される一般的なシステムではないために、ユーザーにとっては複雑すぎるのでしょうか。

7
stijn

特に、例外のスローとエラーオブジェクトの返却には大きな違いがあります。

  • 例外は、ソースコードでは明示的に表示されない多くの新しいコードフローパスを作成します。
  • 呼び出し元は、どのようなエラーが発生する可能性があるかわからない、
  • それらは自動的に伝播し、
  • これにより、コードが非常に簡潔になり、読み書きしやすくなります。

Rustは、例外を禁止することで(これにより最初の2つのポイントが排除され)、try!マクロを持つことで(これは自動伝播と冗長性を扱います)、両方の世界を最大限に活用しようとします。

ソリューションは最初の2つのポイントのみを扱い、後の2つを簡単に設定する方法はありません。 *演算子はtry!と同等ではありません。同等のもの

x = try!(foo());

c ++では(言語拡張なしでは)不可能です。

つまり、クラスを自由に使用してください。ただし、@ Deduplicatorが指摘しているように、Resultクラスは動的型std::exceptionの例外オブジェクトしか保持できません。これは、おそらく意図したものではありません。あなたが望むことを達成する最も簡単な方法は、std::exception_ptrを使用して例外オブジェクトを保持することです。

または、さらに良いことに、例外を使用するだけです-結局のところ、それは言語がそのために設計されたものです。

4
avakar

まあ、それはおそらく役立つでしょう。関数が例外を使用してはならない場合。

しかし、あなたのタイプはすべて間違っています:

  1. 例外を動的に割り当てる(または少なくとも動的に割り当てられるようにする)ことで、サブタイプを返すことができます。
  2. 相互に排他的なオプションを互いに積み重ねないでください。スペースが無駄になるだけです。そして、スタックスペースは実際にはいくらか価値があります。 Boost.Variant を見てください。

#2を修正すると、#1の小さな例外の最適化を実行できるため、ほとんどの場合、動的割り当てを回避できます。

1
Deduplicator