8

out パラメーターよりも戻り値を使用する方が望ましいという多くの引数を見てきました。それらを回避する理由については確信がありますが、回避できないケースに遭遇しているかどうかはわかりません。

私の質問のパート 1は次のとおりです。out パラメータを使用して回避するためのお気に入りの/一般的な方法は何ですか? 線に沿ったもの:男、ピアレビューで、他のプログラマーがこの方法で簡単にできるときにこれを行うのをいつも見ています。

私の質問のパート 2では、out パラメータを回避したいが、そうするためのクリーンな方法を考えられない場合に遭遇したいくつかの特定のケースを扱います。

例 1: コストのかかるコピーを避けたいクラスがあります。オブジェクトに対して作業を行うことができますが、これによりオブジェクトが構築され、コピーにコストがかかります。データを構築する作業も簡単ではありません。現在、このオブジェクトを、オブジェクトの状態を変更する関数に渡します。これは、オブジェクトをワーカー関数の内部で新しくして元に戻すよりも望ましい方法です。

class ExpensiveCopy //Defines some interface I can't change.
{
public:
    ExpensiveCopy(const ExpensiveCopy toCopy){ /*Ouch! This hurts.*/ };
    ExpensiveCopy& operator=(const ExpensiveCopy& toCopy){/*Ouch! This hurts.*/};

    void addToData(SomeData);
    SomeData getData();
}

class B
{
public:
    static void doWork(ExpensiveCopy& ec_out, int someParam);
    //or
    // Your Function Here.
}

私の関数を使用して、次のような呼び出しコードを取得します。

const int SOME_PARAM = 5;
ExpensiveCopy toModify;
B::doWork(toModify, SOME_PARAM);

私はこのようなものが欲しいです:

ExpensiveCopy theResult = B::doWork(SOME_PARAM);

しかし、これが可能かどうかはわかりません。

2 番目の例: オブジェクトの配列があります。配列内のオブジェクトは複合型であり、各要素に対して作業を行う必要があり、各要素にアクセスするメイン ループから分離しておきたい作業です。コードは現在次のようになっています。

std::vector<ComplexType> theCollection;
for(int index = 0; index < theCollection.size(); ++index)
{
    doWork(theCollection[index]);
}

void doWork(ComplexType& ct_out)
{
   //Do work on the individual element.
}

これらの状況のいくつかに対処する方法について何か提案はありますか? 私は主に C++ で作業していますが、他の言語で簡単にセットアップできるかどうかに興味があります。考えられる解決策として RVO に遭遇しましたが、それについてもっと読む必要があり、コンパイラ固有の機能のように思えます。

4

9 に答える 9

7

ここで参照を渡さないようにしようとしている理由がわかりません。参照渡しのセマンティクスが存在するのは、ほとんどこれらの状況です。

コード

static void doWork(ExpensiveCopy& ec_out, int someParam);

私には完璧に見えます。

本当に変更したい場合は、いくつかのオプションがあります

  1. ExpensiveCopy のメンバーになるように doWork を移動します (これはできないと言うので、それはアウトです)。
  2. コピーする代わりに、doWork から (スマート) ポインターを返します。(スタックに物を保持したいので、やりたくない)
  3. RVOに頼る
于 2010-01-26T16:37:53.580 に答える
3

最適化が有効になっている場合、すべての有用なコンパイラは RVO (戻り値の最適化) を行います。

Expensive work() {
    // ... no branched returns here
    return Expensive(foo);
}

Expensive e = work();

場合によっては、コンパイラは、戻り値の最適化という名前の NRVO も適用できます。

Expensive work() {
    Expensive e; // named object
    // ... no branched returns here
    return e; // return named object
}

ただし、これは正確に信頼できるわけではなく、より些細なケースでのみ機能するため、テストする必要があります。すべてのケースをテストする準備が整っていない場合は、2 番目のケースで out-parameters と参照を使用してください。

于 2010-01-26T16:42:32.117 に答える
2

In addition to all comments here I'd mention that in C++0x you'd rarely use output parameter for optimization purpose -- because of Move Constructors (see here)

于 2010-01-26T16:57:55.943 に答える
2

IMO 最初に自問すべきことは、コピーExpensiveCopyが本当に法外な費用がかかるかどうかです。それに答えるには、通常、プロファイラーが必要です。コピーが本当にボトルネックであるとプロファイラーが教えてくれない限り、読みやすいコードを書くだけですExpensiveCopy obj = doWork(param);

もちろん、パフォーマンスやその他の理由でオブジェクトをコピーできない場合もあります。次に、ニールの答えが適用されます。

于 2010-01-26T16:42:38.243 に答える
1

「すべてが不変」というルートをたどる場合を除き、C++ にはあまり適していません。パラメータを簡単に回避することはできません。C++ 標準ライブラリはそれらを使用しており、それで十分なものは私にとって十分です。

于 2010-01-26T16:38:56.727 に答える
0

私が見る限り、出力パラメーターよりも戻り値を好む理由は、それがより明確であり、純粋関数型プログラミングで機能するためです(関数が入力パラメーターのみに依存し、値を返し、副作用なし)。最初の理由は文体であり、私の意見ではそれほど重要ではありません。2つ目は、C++には適していません。したがって、パラメータを回避するために何かを歪めようとはしません。

単純な事実は、いくつかの関数は複数のものを返さなければならないということです、そしてほとんどの言語でこれはパラメータを示唆します。Common Lispにはとがmultiple-value-bindありmultiple-value-return、シンボルのリストがバインドによって提供され、値のリストが返されます。場合によっては、関数が値のリストなどの複合値を返すことがあり、それが分解されます。C++関数がを返すことは大したことではありませんstd::pair。C++でこの方法で3つ以上の値を返すのは厄介です。構造体を定義することはいつでも可能ですが、それを定義して作成することは、多くの場合、パラメーターを出力するよりも面倒です。

場合によっては、戻り値が過負荷になります。Cではgetchar()、intを返します。charよりもint値が多いという考えで(私が知っているすべての実装でtrue、簡単に想像できるものではfalse)、値の1つを使用してend-ofを表すことができます。 -ファイル。 atoi()渡された文字列で表される整数、または渡されない場合はゼロのいずれかの整数を返すため、「0」と「カエル」に対して同じものを返します。(int値があったかどうかを知りたい場合strtol()は、outパラメーターを持つを使用してください。)

エラーが発生した場合に例外をスローする手法は常にありますが、複数の戻り値がすべてエラーであるとは限りません。また、すべてのエラーが例外であるとは限りません。

したがって、オーバーロードされた戻り値は問題を引き起こし、複数の値の戻りはすべての言語で簡単に使用できるわけではなく、単一の戻りが常に存在するとは限りません。多くの場合、例外をスローすることは不適切です。多くの場合、パラメータを使用するのが最もクリーンなソリューションです。

于 2010-01-26T17:02:03.070 に答える
0

そもそも、コピーするのにコストがかかるこのオブジェクトに対して作業を実行するメソッドがある理由を自問してください。木があるとします。その木を何らかの構築方法に送りますか、それとも木に独自の構築方法を与えますか? このような状況は、デザインが少しずれていると常に発生しますが、それを抑えると折りたたむ傾向があります。

実際には、常にすべてのオブジェクトをまったく変更できるとは限らないことはわかっていますが、パラメーターを渡すことは副作用操作であり、何が起こっているのかを理解するのがはるかに難しくなり、実際にそれを行う必要はありません (ただし、他のコード フレームワーク内で作業することによって強制される場合を除きます)。

より簡単な場合もありますが、理由もなくそれを使用することは絶対に望ましくありません (常に半ダースの出力パラメーターが存在するいくつかの大規模プロジェクトに苦しんでいる場合は、私の言いたいことがわかるでしょう)。

于 2010-01-26T19:40:44.713 に答える
0

どのプラットフォームで作業していますか?

私が尋ねる理由は、多くの人が戻り値の最適化を提案しているためです。これは、ほとんどすべてのコンパイラに存在する非常に便利なコンパイラ最適化です。さらに、Microsoft と Intel は、さらに便利な Named Return Value Optimization と呼ばれるものを実装しています。

標準の戻り値の最適化では、return ステートメントはオブジェクトのコンストラクターへの呼び出しであり、一時的な値 (必ずしもコピー操作ではありません) を削除するようコンパイラーに指示します。

名前付き戻り値の最適化では、名前で値を返すことができ、コンパイラは同じことを行います。NRVO の利点は、作成された値に対して、それを返す前により複雑な操作 (関数の呼び出しなど) を実行できることです。

返されるデータが非常に大きい場合、どちらも実際にはコストのかかるコピーをなくすことはできませんが、役に立ちます。

コピーを回避するという点では、それを行う唯一の実際の方法は、ポインターまたは参照を使用することです。これは、関数が最終的に配置したい場所でデータを変更する必要があるためです。参照パラメーター。

また、特にこの理由から、参照渡しは高性能コードで非常に一般的であることを指摘しておく必要があると思います。データのコピーは非常にコストがかかる可能性があり、コードを最適化するときに見落とされることがよくあります。

于 2010-01-26T16:55:30.663 に答える
0

最初の例については、戻り値の最適化により、オブジェクトをコピーする代わりに、返されたオブジェクトを直接その場で作成できることがよくあります。最新のコンパイラはすべてこれを行います。

于 2010-01-26T16:40:35.880 に答える