12

シンク引数として渡された大量のデータを操作する関数があります。私のBigData型は既に C++11 に対応しており、完全に機能するムーブ コンストラクターとムーブ代入の実装が付属しているため、いまいましいものをコピーする必要はありません。

Result processBigData(BigData);

[...]

BigData b = retrieveData();
Result r = processBigData(std::move(b));

これはすべて完全に正常に機能します。ただし、処理関数が実行時に失敗して例外が発生することがあります。問題を修正して再試行するだけなので、これは実際には問題ではありません。

BigData b = retrieveData();
Result r;
try {
    r = processBigData(std::move(b));
} catch(std::runtime_error&) {
    r = fixEnvironmnentAndTryAgain(b);
    // wait, something isn't right here...
}

もちろん、これはうまくいきません。

データを処理関数に移動したため、例外ハンドラーに到達するまでにはb使用できなくなります。

これにより、シンク引数を値で渡すことに対する私の熱意が大幅に低下する恐れがあります。

そこで質問があります: 最新の C++ コードでこのような状況に対処するにはどうすればよいでしょうか? 以前に実行に失敗した関数に移動されたデータへのアクセスを取得する方法は?

BigDataとの両方の実装とインターフェースを好きなように変更processBigDataできます。ただし、最終的な解決策では、効率と使いやすさに関する元のコードの欠点を最小限に抑える必要があります。

4

2 に答える 2

3

私も同様に、この問題に不満を持っています。

私の知る限り、現在の最良のイディオムは、値渡しを参照渡しのペアに分割することです。

template< typename t >
std::decay_t< t >
val( t && o ) // Given an object, return a new object "val"ue by move or copy
    { return std::forward< t >( o ); }

Result processBigData(BigData && in_rref) {
    // implementation
}

Result processBigData(BigData const & in_cref ) {
    return processBigData( val( in_cref ) );
}

もちろん、引数の一部が例外の前に移動されている可能性があります。問題は、すべての呼び出しに伝播しprocessBigDataます。

特定の例外が発生した場合に自身をソースに戻すオブジェクトを開発するというインスピレーションがありましたが、それは私のプロジェクトの 1 つに見られる特定の問題に対する解決策です。専門的になりすぎたり、まったく実行不可能になる可能性があります。

于 2014-09-04T14:05:42.143 に答える
2

この問題は、最近の CppCon 2014 で活発に議論されたようです。Herb Sutter は、クロージング トークBack to the Basics!で最新の状況をまとめました。最新の C++ スタイルの要点(スライド) .

彼の結論は非常に単純です。シンク引数に値渡しを使用しないでください。

そもそもこの手法を使用することについての議論 (Eric Niebler の Meeting C++ 2013 基調講演C++11 Library design (slides)で広く知られているように) は、欠点の方が勝っているようです。const&シンク引数を値渡しする最初の動機は、 /を使用した結果生じる関数オーバーロードの組み合わせ爆発を取り除くことでした&&

残念ながら、これは多くの意図しない結果をもたらすようです。そのうちの 1 つは潜在的な効率の欠点です (主に不要なバッファー割り当てによる)。もう1つは、この質問からの例外安全性の問題です。これらはどちらも Herb の講演で説明されています。

Herb の結論は、シンク引数に値渡しを使用せconst&、代わりに個別の/に依存することです&&(const&これはデフォルトであり&&、最適化が必要ないくつかのケースのために予約されています)。

これは、 @Potatoswatterの回答が示唆したものとも一致します。経由でシンク引数を渡すこと&&で、引数からのデータの実際の移動を、noexcept 保証を与えることができるポイントまで遅らせることができるかもしれません。

シンク引数を値で渡すというアイデアは気に入りましたが、実際には誰もが望んでいたほどうまく機能していないようです。

これについて5年間考えた後、更新します。

私の動機付けの例は、移動セマンティクスの誤用であると確信しています。の呼び出し後、たとえ関数が例外で終了したとしても、processBigData(std::move(b));の状態が何であるかを仮定することは決して許されるべきではありません。bそうすることで、従うのも維持するのも難しいコードにつながります。

代わりにb、エラーの場合に の内容を回復可能にする必要がある場合は、これをコードで明示する必要があります。例えば:

class BigDataException : public std::runtime_error {
private:
    BigData b;
public:
    BigData retrieveDataAfterError() &&;

    // [...]
};


BigData b = retrieveData();
Result r;
try {
    r = processBigData(std::move(b));
} catch(BigDataException& e) {
    b = std::move(e).retrieveDataAfterError();
    r = fixEnvironmnentAndTryAgain(std::move(b));
}

の内容を復元したい場合はb、エラー パスに沿って明示的に渡す必要があります (この場合は 内にラップされていますBigDataException)。このアプローチには定型文が少し必要ですが、移動元オブジェクトの状態について仮定する必要がないという点で、より慣用的です。

于 2014-09-17T12:06:12.797 に答える