44

私はタプルに頭を悩ませようとしています(@litbに感謝します)。タプルの使用に関する一般的な提案は、> 1の値を返す関数です。

これは私が通常構造体を使用するものであり、この場合のタプルの利点を理解できません.末期の怠惰な人にとってはエラーが発生しやすいアプローチのようです.

例を借りて、これを使用します

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
};

...これでは自信が持てません。

では、あいまいさを補う構造体に対するタプルの利点はでしょうか?

4

9 に答える 9

24

タプル

どの位置がどの変数に対応するかという問題が混乱を招く可能性があることに同意します。しかし、私は2つの側面があると思います。1 つは呼び出し側で、もう1 つは呼び出し先側です。

int remainder; 
int quotient;
tie(quotient, remainder) = div(10, 3);

何が得られたかは明らかだと思いますが、一度により多くの値を返さなければならない場合、混乱を招く可能性があります。呼び出し元のプログラマーが のドキュメントを参照divすると、どの位置が何なのかがわかり、効果的なコードを書くことができます。経験則として、一度に 4 つを超える値を返さないようにします。それ以上のものについては、構造体を優先してください。

出力パラメータ

もちろん、出力パラメータも使用できます。

int remainder; 
int quotient;
div(10, 3, &quotient, &remainder);

これは、タプルが出力パラメーターよりも優れていることを示していると思います。の入力とその出力を混合しましたが、div何の利点も得られませんでした。さらに悪いことに、be の実際の戻り値が何であるかについて、そのコードの読者に疑問を抱かせdivます。出力パラメーターが役立つ素晴らしい例あります。私の意見では、戻り値は既に取得されており、タプルにも構造体にも変更できないため、他に方法がない場合にのみ使用する必要があります。operator>>戻り値はすでにストリーム用に予約されているため、出力パラメーターを使用する場所の良い例です。operator>>呼び出します。演算子を使用したことがなく、コンテキストが明確でない場合は、ポインターを使用して、オブジェクトが実際に出力パラメーターとして使用されていることを呼び出し側で通知することをお勧めします。また、必要に応じてコメントを追加することをお勧めします。

構造体を返す

3 番目のオプションは、構造体を使用することです。

div_result d = div(10, 3);

私は間違いなくクリアネス賞を受賞すると思います。ただし、その構造体内の結果にアクセスする必要があることに注意してください。出力パラメーターと で使用されるタプルの場合と同様に、結果はテーブルに「むき出し」ではありませんtie

最近の重要なポイントは、すべてを可能な限り汎用的にすることだと思います。たとえば、タプルを出力できる関数があるとします。あなたはただすることができます

cout << div(10, 3);

そして、結果を表示します。反対に、タプルはその用途の広い性質のために明らかに勝っていると思います。div_result でそれを行うには、operator<< をオーバーロードするか、各メンバーを個別に出力する必要があります。

于 2009-01-03T21:53:37.980 に答える
10

もう 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を参照してください。

于 2009-01-03T22:48:45.843 に答える
5

タプルを使用するtieと、を使用できます。これは、非常に便利な場合がありますstd::tr1::tie (quotient, remainder) = do_division ();。これは構造体ではそれほど簡単ではありません。次に、テンプレートコードを使用する場合、構造体型にさらに別のtypedefを追加するよりも、ペアに依存する方が簡単な場合があります。

また、タイプが異なる場合、ペア/タプルは実際には構造体よりも悪くはありません。たとえばpair<int, bool> readFromFile()、intは読み取られたバイト数であり、boolはeofがヒットしたかどうかです。この場合に構造体を追加することは、特にここにあいまいさがないため、私にはやり過ぎのように思えます。

于 2009-01-03T21:05:19.093 に答える
4

タプルは、ML や Haskell などの言語で非常に役立ちます。

C++ では、それらの構文により洗練されていませんが、次の状況で役立ちます。

  • 複数の引数を返す必要がある関数がありますが、結果は呼び出し元と呼び出し先に対して「ローカル」です。このためだけに構造を定義したくない

  • tie 関数を使用して、非常に限定された形式のパターン マッチング "a la ML" を実行できます。これは、同じ目的で構造体を使用するよりもエレガントです。

  • 定義済みの < 演算子が付属しているため、時間を節約できます。

于 2009-01-03T21:46:26.343 に答える
3

「名前のないタプル」の問題を少なくとも部分的に軽減するために、タプルを 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);
于 2009-01-03T22:27:15.090 に答える
3

構造体にはないタプルの機能の 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
}

コンパイラは の結果で "Tuple" を初期化できないmake_tupleため、3 番目のパラメータに正しい値を指定できるようにするエラーが生成されます。

最後に、タプルのもう 1 つの利点は、各値を反復処理するコードを記述できることです。これは、構造体を使用するだけでは不可能です。

void incrementValues (boost::tuples::null_type) {}

template <typename Tuple_>
void incrementValues (Tuple_ & tuple) {
   // ...
   ++tuple.get_head ();
   incrementValues (tuple.get_tail ());
}
于 2009-06-25T10:04:57.953 に答える
2

コードに多くの構造体定義が散らばるのを防ぎます。独自の構造体を作成したり、構造体の定義を調べたりするよりも、コードを作成する人にとって、およびタプルの各要素が何であるかを文書化するだけでコードを使用する方が簡単です。

于 2009-01-03T21:08:22.410 に答える
2

タプルは記述が簡単になります。何かを返す関数ごとに新しい構造体を作成する必要はありません。何がどこに行くかについてのドキュメントは、とにかく必要になる関数のドキュメントに行きます。関数を使用するには、どのような場合でも関数のドキュメントを読む必要があり、そこでタプルについて説明します。

于 2009-01-03T21:19:15.023 に答える
-1

私はあなたに100%ロディに同意します。

メソッドから複数の値を返すには、タプル以外のいくつかのオプションがあります。これらのオプションは、ケースによって異なります。

  1. 新しい構造体を作成します。これは、返す複数の値が関連している場合に役立ち、新しい抽象化を作成するのが適切です。たとえば、「divide_result」は一般的な抽象化として優れていると思います。このエンティティを渡すと、名前のないタプルを渡すよりもコードがはるかに明確になります。次に、この新しい型を操作するメソッドを作成したり、他の数値型に変換したりできます。

  2. 「Out」パラメータを使用します。参照によっていくつかのパラメーターを渡し、各outパラメーターに割り当てることによって複数の値を返します。これは、メソッドがいくつかの無関係な情報を返す場合に適しています。この場合、新しい構造体を作成するのはやり過ぎです。Outパラメーターを使用すると、この点を強調し、さらに各アイテムに適切な名前を付けます。

タプルは悪です。

于 2009-01-03T21:24:45.933 に答える