Подтвердить что ты не робот

Что не так с передачей итератора С++ по ссылке?

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

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.

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

4b9b3361

Ответ 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

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

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

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