Что не так с передачей итератора С++ по ссылке?
Я написал несколько функций с таким прототипом:
template <typename input_iterator>
int parse_integer(input_iterator &begin, input_iterator end);
Идея состоит в том, что вызывающая сторона будет предоставлять диапазон символов, а функция будет интерпретировать символы как целочисленное значение и возвращать его, оставляя begin
за последним последним использованным символом. Например:
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
.
С тех пор мне сказали (без объяснения причин), что передавать итератор по ссылке - это плохо. Это плохая форма? Если так, то почему?
Ответы
Ответ 1
Нет ничего действительно неправильного, но это, безусловно, ограничит использование шаблона. Вы не сможете просто поставить итератор, возвращаемый чем-то другим, или сгенерированный как 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));
Ответ 2
В целом:
Если вы передаете ссылку не const
, вызывающий объект не знает, изменяется ли итератор.
Вы можете передать ссылку const
, но обычно итераторы достаточно малы, что не дает преимущества перед передачей по значению.
В вашем случае:
Я не думаю, что что-то не так с тем, что вы делаете, за исключением того, что это не слишком стандартное использование итератора.
Ответ 3
Когда они говорят: "Не проходите по ссылке", возможно, потому что более нормальный/идиоматический переход итераторов в качестве параметров значения, вместо того, чтобы передавать их по ссылке const, что вы сделали, для второго параметра.
В этом примере, однако, вам нужно вернуть два значения: значение синтаксического анализа, а также новое/измененное значение итератора; и учитывая, что функция не может иметь два кода возврата, кодирование одного из кодов возврата в качестве неконстантной ссылки является нормальным ИМО.
Альтернативой было бы кодировать его примерно так:
//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)
{
}
Ответ 4
На мой взгляд, если вы хотите сделать это, аргумент должен быть указателем на итератор, который вы будете менять. Я не являюсь большим поклонником неконстантных опорных аргументов, потому что они скрывают тот факт, что передаваемый параметр может измениться. Я знаю, что есть много пользователей С++, которые не согласны с моим мнением об этом - и это прекрасно.
Однако в этом случае так, чтобы итераторы рассматривались как аргументы значения, я считаю, что особенно плохой идеей передавать итераторы неконстантной ссылкой и изменять переданный итератор. Это просто противоречит идиоматическому способу использования итераторов.
Поскольку существует отличный способ сделать то, что вы хотите, у которого нет этой проблемы, я думаю, вы должны использовать его:
template <typename input_iterator>
int parse_integer(input_iterator* begin, input_iterator end);
Теперь вызывающий абонент должен будет сделать:
int i = parse_integer(&p, end);
И будет очевидно, что итератор можно изменить.
Кстати, мне также нравится litb suggestion вернуть новый итератор и поместить разобранные значения в место, указанное выходным итератором.
Ответ 5
В этом контексте я считаю, что передача итератора по ссылке является вполне разумной, если она хорошо документирована.
Стоит отметить, что ваш подход (передача итератора по ссылке, чтобы отслеживать, где вы находитесь, когда токенизируете поток) - это именно тот подход, который берется повышение:: Tokenizer. В частности, см. Определение Концепция TokenizerFunction. В целом, я нахожу boost:: tokenizer очень хорошо разработанным и продуманным.
Ответ 6
Я думаю, что алгоритмы стандартной библиотеки передают итераторы по значению исключительно (кто-то теперь опубликует очевидное исключение) - это может быть источником идеи. Конечно, ничто не говорит о том, что ваш собственный код должен выглядеть как стандартная библиотека!
Ответ 7
Во втором параметре объявления функции отсутствует ссылка, не так ли?
В любом случае, вернемся к вашему вопросу: Нет, я никогда не читал ничего, что говорит, что вы не должны передавать итераторы по ссылке. Проблема со ссылками заключается в том, что они позволяют вам изменять ссылочный объект. В этом случае, если вы хотите изменить итератор, вы потенциально завинчиваете всю последовательность за пределами этой точки, тем самым делая невозможной дальнейшую обработку.
Только одно предложение: тщательно введите свои параметры.