9

一般的な例外とエラー コードの議論に入る前に、何人のGo 開発者が明らかにエラー処理を行っているように、複数の値、つまり関数の戻り値とエラー/成功コードを使用std::pairまたは返すことの欠点は何だと思いますか?std:tuple

このアプローチには明らかに、関数の戻り値またはエラー コードに out パラメーターを使用する必要がないという利点があります (どちらを好むかによって異なります)。

4

4 に答える 4

2

この目的のために、ほとんどの場合、シンタックス シュガーを導入する独自のラッパー タイプを使用します。例を見てみましょう:

template <class T>
struct Result
{
public:
    enum Status {
        Success,
        Error
    };

    // Feel free to change the default behavior... I use implicit
    // constructors for type T for syntactic sugar in return statements.
    Result(T resultValue) : s(Success), v(resultValue) {}
    explicit Result(Status status, std::string errMsg = std::string()) : s(status), v(), errMsg(errMsg) {}
    Result() : s(Error), v() {} // Error without message

    // Explicit error with message
    static Result error(std::string errMsg) { return Result(Error, errMsg); }

    // Implicit conversion to type T
    operator T() const { return v; }
    // Explicit conversion to type T
    T value() const { return v; }

    Status status() const { return s; }
    bool isError() const { return s == Error; }
    bool isSuccessful() const { return s == Success; }
    std::string errorMessage() const { return errMsg; }

private:
    T v;
    Status s;

    // if you want to provide error messages:
    std::string errMsg;
};

次に、エラーを返す可能性のあるメソッドの戻り値としてこのクラスを使用します。

Result<int> fac(int n) {
    if(n < 0)
        return Result<int>::error("n has to be greater or equal zero!");
    if(n == 0)
        return 1;
    if(n > 0)
        return n * fac(n-1);  // gets automatically converted to int
}

もちろん、階乗関数のこの実装はひどいものですが、使用するエラー拡張された戻り値の型を気にせずに変換を示しています。

使用例:

int main() {
    for(int i = -3; i < 4; ++i)
    {
        Result<int> r = fac(i);
        std::cout << i << " | ";
        std::cout << (r.isSuccessful() ? "ok" : "error") << " | ";
        if(r.isSuccessful())
            std::cout << r.value();
        else
            std::cout << r.errorMessage();
        std::cout << std::endl;
    }
}

出力:

-3 | error | n has to be greater or equal zero!
-2 | error | n has to be greater or equal zero!
-1 | error | n has to be greater or equal zero!
0 | ok | 1
1 | ok | 1
2 | ok | 2
3 | ok | 6

カスタム型の大きな利点の 1 つは、クライアント コードが実際の値にアクセスする前に常にエラーをチェックし、成功した場合にのみ値にアクセスし、そうでない場合はエラー メッセージにアクセスすることを保証するコントロールを挿入できることです。このために、次のようにクラスを拡張できます。

struct Result
{
public:
    // in all constructors, add:
    Result(...) : ..., checked(false) {...}

    // in the error checker methods, add: (and drop const-ness)
    bool is...() { checked = true; return ... }

    // rewrite the value conversion as follows:
    operator T() const { std::assert(checked && isSuccessful()); return v; }
    T value() const    { std::assert(checked && isSuccessful()); return v; }

    // rewrite the errorMessage-getter as follows:
    std::string errorMessage() const { std::assert(checked && isError()); return errMsg; }

private:
    ...
    bool checked;
};

ビルドモード(デバッグビルド/リリースビルド)に応じてクラス定義を作成したい場合があります。

この例は次のように書き直す必要があることに注意してください。

Result<int> fac(int n) {
    if(n < 0)
        return Result<int>::error("n has to be greater or equal zero!");
    if(n == 0)
        return 1;
    if(n > 0) {
        Result<int> r = fac(n - 1);
        if(r.isError()) return r;  // propagate error (similar to exceptions)
        return n * r;              // r gets automatically converted to int
    }
}

上記のメインコードは、値/エラーメッセージにアクセスする前に既にエラーチェックを行っているため、引き続き有効です。

于 2012-12-08T14:14:22.243 に答える
2

タイプと成功インジケータの両方が関数の戻り値であるため、この「イディオム」は適切です。失敗は例外的ではないかもしれないので、例外は不適切な場合があります。

ただし、欠点は、2 つの戻り値の型を分割する必要があることです。これは醜い場合があります。ヘルプを使用してstd::tieいますが、複数のリターンから構築することはできません。

bool success;
std::string value;
std::tie(success, value)=try_my_func();

これは非常に冗長です。

第 2 に、タプル内の別の要素の値に応じてタイプの 1 つが「オプション」である場合でも、それを構築する必要があり、一部のタイプでは依然として非常に無駄です。

イディオムをよく使用している場合は、代わりにboost::optional型のようなものを使用することを検討してください。これは、おそらく go の複数のリターンよりも haskel に近いです。

参照

http://www.boost.org/doc/libs/1_52_0/libs/optional/doc/html/index.html

于 2012-12-08T13:56:21.570 に答える
1

「複数の値、つまり関数の戻り値とエラー/成功コードを返すためにstd :: pairまたはstd:tupleを使用することの欠点は何だと思いますか?」

障害処理に対するその単純な(Cレベル)アプローチの主な欠点は、

  • 安全性の喪失。

つまり、不確定な結果値にアクセスするなど、うまくいかない可能性のあるものが他にもあります。または、関数が意味のある値を生成しなかったときに戻り値を使用するだけです。

古いBarton&NackmanFallowクラスは、結果値へのアクセスを制限することにより、この安全性の問題を解決しました。基本的に、呼び出し元のコードは、結果値を使用する前に結果値があるかどうかを確認する必要があり 論理的に存在しない結果値を使用すると、例外または終了が発生します。boost::optionalクラスはほとんど同じことをします。

Boostに依存したくない場合はOptional、POD結果タイプにクラスを実装するのは簡単です。少し非効率(動的割り当て)を犠牲にして、を使用しstd::vectorて非PODの可能性のある結果を運ぶことができます。

課題は、呼び出しコードの明確さを維持することです。これが演習の要点です…

于 2012-12-08T15:27:29.190 に答える
0

パラメータなしで人生を無駄にする必要がないため、通常のエラーコードよりも優れています。しかし、それでも非常に深刻な欠点がすべて残っています。

実際には、これはほんのわずかな変更であり、エラー コードのままです。大きなメリットはありません。

于 2012-12-08T14:46:22.140 に答える