Что плохого в передаче итератора C++ по ссылке?

StackOverflow https://stackoverflow.com/questions/843996

  •  20-08-2019
  •  | 
  •  

Вопрос

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

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.

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

Это было полезно?

Решение

В этом нет ничего плохого, но это, безусловно, ограничит использование шаблона.Вы не сможете просто поместить итератор, возвращаемый чем-то другим или сгенерированный, например 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));

Другие советы

В общем:

Если вы пройдете не-const ссылка, вызывающая сторона не знает, изменяется ли итератор.

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

В твоем случае:

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

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

Однако в этом примере вам нужно вернуть два значения:проанализированное значение 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)
{
}

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

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

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

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

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

int i = parse_integer(&p, end);

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

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

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

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

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

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

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

Всего одно предложение:внимательно вводите параметры.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top