web-dev-qa-db-ja.com

参照でC ++イテレータを渡すことの何が問題になっていますか?

私はこのようなプロトタイプでいくつかの関数を書きました:

template <typename input_iterator>
int parse_integer(input_iterator &begin, input_iterator end);

アイデアは、呼び出し側が一連の文字を提供し、関数が文字を整数値として解釈してそれを返し、beginを最後に使用された文字の1つ前に残すというものです。例えば:

std::string sample_text("123 foo bar");
std::string::const_iterator p(sample_text.begin());
std::string::const_iterator end(sample_text.end());
int i = parse_integer(p, end);

これにより、iが123に設定され、pfooの前のスペースを「ポイント」します。

それ以来、私は(説明なしで)参照によってイテレータを渡すのは悪い形だと言われてきました。悪い形ですか?もしそうなら、なぜですか?

40
Adrian McCarthy

本当に問題はありませんが、テンプレートの使用が制限されることは確かです。これらは一時的なものであるため、v.begin()のように、他の何かによって返されたり、生成されたイテレータを置くだけではできません。常に最初にローカルコピーを作成する必要があります。これは、ある種のボイラープレートであり、実際に使用するのは適切ではありません。

1つの方法は、それをオーバーロードすることです。

int parse_integer(input_iterator begin, input_iterator end, 
                  input_iterator &newbegin);

template<typename input_iterator>
int parse_integer(input_iterator begin, input_iterator end) {
    return parse_integer(begin, end, begin);
} 

別のオプションは、番号が書き込まれる出力イテレータを持つことです:

template<typename input_iterator, typename output_iterator>
input_iterator parse_integer(input_iterator begin, input_iterator end,
                             output_iterator out);

新しい入力反復子を返すための戻り値があります。そして、インサータイテレータを使用して解析した数値をベクトルに入れるか、すでに数値がわかっている場合はポインタを整数またはその配列に直接入れることができます。

int i;
b = parse_integer(b, end, &i);

std::vector<int> numbers;
b = parse_integer(b, end, std::back_inserter(numbers));

一般的に:

const以外の参照を渡した場合、呼び出し元はイテレータが変更されているかどうかを知りません。

const参照を渡すこともできますが、通常、反復子は十分に小さいため、値による受け渡しよりも利点がありません。

あなたの場合:

イテレータの使用に関してあまり標準的ではないことを除いて、あなたが何をしていることにも問題はないと思います。

4
Reunanen

私の意見では、これを行う場合、引数は変更するイテレータへのポインタである必要があります。非const参照引数の大ファンではありません。これは、渡されたパラメーターが変更される可能性があるという事実を隠すためです。私はこれについての私の意見に反対する多くのC++ユーザーがいることを知っています-それは問題ありません。

ただし、この場合、イテレータが値引数として扱われるのはsoが一般的であり、非const参照でイテレータを渡すことは特に悪い考えだと思います渡されたイテレータを変更します。イテレータが通常使用される慣用的な方法に反するだけです。

この問題がない、あなたがやりたいことをする素晴らしい方法があるので、それを使うべきだと思います:

template <typename input_iterator>
int parse_integer(input_iterator* begin, input_iterator end);

今、呼び出し元は行う必要があります:

int i = parse_integer(&p, end);

イテレータを変更できることは明らかです。

ちなみに、新しいイテレータを返し、解析した値を出力イテレータで指定された場所に配置する litbの提案 も気に入っています。

2
Michael Burr

この文脈では、十分に文書化されている限り、参照によってイテレータを渡すことは完全に賢明であると思います。

あなたのアプローチ(ストリームをトークン化するときにあなたがどこにいるかを追跡するために参照によってイテレータを渡す)は、まさに boost :: tokenizer が採用するアプローチであることは注目に値します。特に、 TokenizerFunction Concept の定義を参照してください。全体的に、boost :: tokenizerはかなりよく設計され、よく考えられていると思います。

2
Edward Loper

彼らが「参照渡ししないでください」と言うとき、それはおそらく、2番目のパラメーターに対して、const参照によって渡したのではなく、値パラメーターとしてイテレーターを渡す方が通常/慣用的であるためです。

ただし、この例では、2つの値を返す必要があります。解析されたint値と、新しい/変更されたイテレータ値です。また、関数に2つの戻りコードを含めることはできないため、1つの戻りコードを非const参照としてコーディングするのはIMOの通常の動作です。

別の方法は、次のようにコード化することです。

//Comment: the return code is a pair of values, i.e. the parsed int and etc ...
pair<int, input_iterator> parse(input_iterator start, input_iterator end)
{
}
2
ChrisW

標準ライブラリのアルゴリズムはイテレータを値で排他的に渡すと思います(誰かがこれに明らかな例外を投稿するでしょう)-これがアイデアの起源かもしれません。もちろん、あなた自身のコードが標準ライブラリのように見えなければならないということは何もありません!

1
anon