Boost::Tuples と戻り値の構造体
-
03-07-2019 - |
質問
私はタプルについて理解しようとしています(@litb に感謝します)。タプルの使用に関する一般的な提案は、1 を超える値を返す関数に対するものです。
これは、通常は struct を使用するものですが、この場合のタプルの利点が理解できません。これは、末端の怠け者にとってはエラーが発生しやすいアプローチのようです。
例をお借りして, 、これを使います
struct divide_result {
int quotient;
int remainder;
};
タプルを使用すると、次のようになります
typedef boost::tuple<int, int> divide_result;
しかし、呼び出している関数のコード (コメントを信頼できるほど愚かであればコメント) を読まないと、どの int が商なのか、またその逆もわかりません。むしろ...のように思えます。
struct divide_result {
int results[2]; // 0 is quotient, 1 is remainder, I think
};
...それでは自信が持てません。
だから何 は あいまいさを補う構造体に対するタプルの利点は何ですか?
解決
タプル
私は、どの位置の問題がどの変数に対応するかが混乱を招く可能性があることに同意すると思います。しかし、2つの側面があると思います。 1つは呼び出し側で、もう1つは呼び出し先側です:
int remainder;
int quotient;
tie(quotient, remainder) = div(10, 3);
私たちが得たものは非常に明確だと思いますが、一度により多くの値を返さなければならない場合、混乱する可能性があります。呼び出し元のプログラマーがdiv
のドキュメントを参照すると、どの位置が何であるかがわかり、効果的なコードを作成できます。経験則として、一度に4つを超える値を返さないようにします。それ以上のものについては、構造体をお勧めします。
出力パラメーター
出力パラメータももちろん使用できます:
int remainder;
int quotient;
div(10, 3, "ient, &remainder);
今、タプルが出力パラメータよりも優れていることを示していると思います。 operator>>
の入力とその出力を混合しましたが、利点はありません。さらに悪いことに、tie
beの実際の戻り値が何であるかについて、そのコードの読者に疑問を抱かせます。出力パラメータが有用な場合の素晴らしい例があります 。私の意見では、戻り値はすでに取得されており、タプルまたは構造体に変更できないため、他の方法がない場合にのみ使用する必要があります。 <=>は出力パラメーターを使用する場所の良い例です。これは、戻り値がすでにストリーム用に予約されているため、<=>呼び出しをチェーン化できるためです。演算子を使用せず、コンテキストが明確でない場合、ポインターを使用して、適切なコメントに加えて、オブジェクトが実際に出力パラメーターとして使用されていることを呼び出し側で通知することをお勧めします。
構造体を返す
3番目のオプションは、構造体を使用することです:
div_result d = div(10, 3);
明確さの賞を間違いなく受賞すると思います。ただし、その構造内で結果にアクセスする必要があり、結果は<!> quot; laid bare <!> quotではないことに注意してください。 <=>。
で使用される出力パラメーターとタプルの場合のように、テーブルで最近の大きなポイントは、すべてを可能な限り一般化することだと思います。したがって、タプルを出力できる関数があるとしましょう。あなたはただ行うことができます
cout << div(10, 3);
結果を表示します。一方、タプルは、その汎用性の性質のために明らかに勝つと思います。 div_resultでこれを行うには、operator <!> lt; <!> lt;をオーバーロードするか、各メンバーを個別に出力する必要があります。
他のヒント
もう1つのオプションは、Boost Fusionマップを使用することです(コードは未テスト):
struct quotient;
struct remainder;
using boost::fusion::map;
using boost::fusion::pair;
typedef map<
pair< quotient, int >,
pair< remainder, int >
> div_result;
比較的直感的に結果にアクセスできます:
using boost::fusion::at_key;
res = div(x, y);
int q = at_key<quotient>(res);
int r = at_key<remainder>(res);
他の利点もあります。たとえば、マップのフィールドを繰り返しできるなどです。 doco で詳細をご覧ください。
タプルを使用すると、次のことができます。 tie
, これは場合によっては非常に便利です。 std::tr1::tie (quotient, remainder) = do_division ();
. 。構造体ではこれはそれほど簡単ではありません。次に、テンプレート コードを使用する場合、構造体の型に別の typedef を追加するよりも、ペアに依存する方が簡単な場合があります。
そして、型が異なる場合、ペア/タプルは実際には構造体よりも劣るものではありません。たとえば考えてみましょう pair<int, bool> readFromFile()
, ここで、int は読み取られたバイト数、bool は eof がヒットしたかどうかを表します。この場合、特に曖昧さがないため、構造体を追加するのは私にとってはやりすぎのように思えます。
タプルは、MLやHaskellなどの言語で非常に便利です。
C ++では、その構文によりエレガントさは低下しますが、次のような状況では役立ちます。
-
複数の引数を返す必要がある関数がありますが、結果は<!> quot; local <!> quot;です。呼び出し元と呼び出し先へ。このためだけに構造を定義したくはありません
-
tie関数を使用して、非常に限られた形式のパターンマッチング<!> quot; a ML <!> quot;を実行できます。これは、同じ目的で構造体を使用するよりもエレガントです。
-
これらには事前定義済みの<!> ltが付属しています。演算子、これは時間の節約になります。
私は、「名前のないタプル」問題を少なくとも部分的に軽減するために、タプルを typedef と組み合わせて使用する傾向があります。たとえば、グリッド構造がある場合は次のようになります。
//row is element 0 column is element 1
typedef boost::tuple<int,int> grid_index;
次に、名前付き型を次のように使用します。
grid_index find(const grid& g, int value);
これはやや不自然な例ですが、ほとんどの場合、読みやすさ、明示性、使いやすさの間で適切な中間点に達すると思います。
またはあなたの例では:
//quotient is element 0 remainder is element 1
typedef boost:tuple<int,int> div_result;
div_result div(int dividend,int divisor);
構造体にはないタプルの機能の1つは、初期化にあります。次のようなものを検討してください。
struct A
{
int a;
int b;
};
同等のmake_tuple
コンストラクターを作成しない限り、この構造体を入力パラメーターとして使用するには、まず一時オブジェクトを作成する必要があります:
void foo (A const & a)
{
// ...
}
void bar ()
{
A dummy = { 1, 2 };
foo (dummy);
}
ただし、メンテナンスが何らかの理由で構造体に新しいメンバーを追加する場合を考えてみましょう。
struct A
{
int a;
int b;
int c;
};
集計の初期化のルールは、実際にはコードが変更なしでコンパイルを続けることを意味します。したがって、コンパイラーの助けなしに、この構造体のすべての使用法を検索して更新する必要があります。
タプルと比較してください:
typedef boost::tuple<int, int, int> Tuple;
enum {
A
, B
, C
};
void foo (Tuple const & p) {
}
void bar ()
{
foo (boost::make_tuple (1, 2)); // Compile error
}
コンパイラは<!> quot; Tuple <!> quotを初期化できません。 <=>の結果であるため、3番目のパラメーターに正しい値を指定できるエラーが生成されます。
最後に、タプルの他の利点は、各値を反復処理するコードを作成できることです。これは、構造体を使用しては不可能です。
void incrementValues (boost::tuples::null_type) {}
template <typename Tuple_>
void incrementValues (Tuple_ & tuple) {
// ...
++tuple.get_head ();
incrementValues (tuple.get_tail ());
}
多くの構造体定義が散らばっているコードを防ぎます。コードを書いている人にとっても、タプル内の各要素が何であるかをドキュメント化するだけで、他の人にとっては、自分で構造を書いたり、人々に構造定義を調べさせるよりも簡単です。
タプルは書きやすくなります-何かを返す関数ごとに新しい構造体を作成する必要はありません。どこに行くかについてのドキュメントは、とにかく必要になる関数ドキュメントに行きます。関数を使用するには、いずれの場合でも関数のドキュメントを読む必要があり、タプルについて説明します。
私はあなたに100%ロディに同意します。
メソッドから複数の値を返すには、タプル以外のいくつかのオプションがありますが、どれが最適かはケースによって異なります:
-
新しい構造体の作成。これは、返される複数の値が関連であり、新しい抽象化を作成することが適切な場合に適しています。たとえば、<!> quot; divide_result <!> quot;一般的な抽象化であり、このエンティティを渡すことで、名前のないタプルを渡すだけでなく、コードがより明確になります。次に、この新しい型で動作するメソッドを作成したり、他の数値型に変換したりできます。
-
<!> quot; Out <!> quot;の使用パラメーター。いくつかのパラメーターを参照で渡し、各出力パラメーターに割り当てることで複数の値を返します。これは、メソッドがいくつかの無関係な情報を返す場合に適しています。この場合、新しい構造体を作成するのはやり過ぎです。Outパラメーターを使用すると、この点が強調され、さらに各アイテムにふさわしい名前が付けられます。
タプルは悪。