Что не так с передачей итератора C ++ по ссылке? - PullRequest
37 голосов
/ 10 мая 2009

Я написал несколько функций с таким прототипом:

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

Идея состоит в том, что вызывающая сторона будет предоставлять диапазон символов, а функция будет интерпретировать символы как целочисленное значение и возвращать его, оставляя начало после последнего использованного символа. Например:

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, а p будет "указывать" на пробел перед foo.

С тех пор мне сказали (без объяснения причин), что передавать итератор по ссылке - это плохо. Это плохая форма? Если так, то почему?

Ответы [ 7 ]

30 голосов
/ 10 мая 2009

В этом нет ничего плохого, но это, безусловно, ограничит использование шаблона. Вы не сможете просто поместить итератор, возвращенный чем-то другим или сгенерированный как v.begin(), так как они будут временными. Сначала вы всегда должны будете сделать локальную копию, что является неким хорошим шаблоном.

Один из способов - перегрузить его:

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));
4 голосов
/ 10 мая 2009

В общем:

Если вы передаете ссылку, отличную от const, вызывающая сторона не будет знать, был ли изменен итератор.

Вы можете передать ссылку const, но обычно итераторы достаточно малы, что не дает преимущества перед передачей по значению.

В вашем случае:

Я не думаю, что в том, что вы делаете, есть что-то не так, за исключением того, что это не слишком стандартно для использования итераторов.

2 голосов
/ 20 декабря 2011

В этом контексте, я думаю, что передача итератора по ссылке совершенно разумна, если он хорошо документирован.

Стоит отметить, что ваш подход (передача итератора по ссылке для отслеживания того, где вы находитесь при токенизации потока) - это именно тот подход, который используется boost :: tokenizer . В частности, см. Определение TokenizerFunction Concept . В целом, я считаю, что boost :: tokenizer хорошо продуман и продуман.

2 голосов
/ 10 мая 2009

По моему мнению, если вы хотите сделать это, аргумент должен быть указателем на итератор, который вы будете изменять. Я не большой поклонник неконстантных ссылочных аргументов, потому что они скрывают тот факт, что переданный параметр может измениться. Я знаю, что есть много пользователей C ++, которые не согласны с моим мнением по этому поводу - и это нормально.

Тем не менее, в этом случае , поэтому обычно для итераторов трактуются как аргументы-значения, и я думаю, что это особенно плохая идея - передавать итераторы по неконстантной ссылке и изменять переданный итератор. Это просто идет вразрез с идиоматическим способом, которым обычно используются итераторы.

Поскольку существует отличный способ сделать то, что вы хотите, у которого нет этой проблемы, я думаю, вы должны использовать его:

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

Теперь вызывающему абоненту нужно будет сделать:

int i = parse_integer(&p, end);

И будет очевидно, что итератор можно изменить.

Кстати, мне также нравится предложение litb о возврате нового итератора и помещении проанализированных значений в местоположение, указанное выходным итератором.

2 голосов
/ 10 мая 2009

Когда они говорят «не передавать по ссылке», возможно, это потому, что более нормально / идиоматично передавать итераторы в качестве параметров-значений, а не передавать их по константной ссылке: что вы сделали для второго параметра.

Однако в этом примере вам нужно вернуть два значения: проанализированное значение int и новое / измененное значение итератора; и учитывая, что функция не может иметь два кода возврата, кодирование одного из кодов возврата в качестве неконстантной ссылки является нормой 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)
{
}
1 голос
/ 10 мая 2009

Я думаю, что алгоритмы стандартной библиотеки передают итераторы исключительно по значению (кто-то опубликует очевидное исключение из этого) - это может быть источником идеи. Конечно, ничто не говорит о том, что ваш собственный код должен выглядеть как Стандартная библиотека!

0 голосов
/ 10 мая 2009

Во втором параметре объявления функции отсутствует ссылка, не так ли?

В любом случае, вернемся к вашему вопросу: нет, я никогда не читал ничего, что говорило бы, что вы не должны передавать итераторы по ссылке. Проблема со ссылками заключается в том, что они позволяют изменять ссылочный объект. В этом случае, если вам нужно изменить итератор, вы потенциально можете испортить всю последовательность за этой точкой, что сделает дальнейшую обработку невозможной.

Только одно предложение: внимательно введите параметры.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...