Pregunta

He escrito algunas funciones con un prototipo como este:

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

La idea es que la persona que llama proporcionará un rango de caracteres, y la función interpretará los caracteres como un valor entero y lo devolverá, dejando que comience en el pasado el último carácter utilizado. Por ejemplo:

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);

Esto dejaría i establecido en 123 y p " señalando " en el espacio antes de foo.

Desde entonces me han dicho (sin explicación) que es una mala forma pasar un iterador por referencia. ¿Es mala forma? Si es así, ¿por qué?

¿Fue útil?

Solución

No hay nada realmente malo, pero ciertamente limitará el uso de la plantilla. No podrá colocar un iterador devuelto por otra cosa o generado como v.begin(), ya que serán temporales. Siempre tendrá que hacer primero una copia local, que es una especie de repetitivo que no es realmente agradable tener.

Una forma es sobrecargarlo:

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);
} 

Otra opción es tener un iterador de salida donde se escribirá el número:

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

Tendrá el valor de retorno para devolver el nuevo iterador de entrada. Y luego podría usar un iterador de inserción para colocar los números analizados en un vector o un puntero para colocarlos directamente en un entero o una matriz de los mismos si ya conoce la cantidad de números.

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

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

Otros consejos

En general:

Si pasa una referencia que no sea const, la persona que llama no sabe si se está modificando el iterador.

Puede pasar una referencia <=>, pero por lo general los iteradores son lo suficientemente pequeños como para que no tenga ninguna ventaja sobre pasar por valor.

En su caso:

No creo que haya nada malo con lo que haces, excepto que no es demasiado estándar con respecto al uso del iterador.

Cuando dicen " no pase por referencia " tal vez sea porque es más normal / idiomático pasar iteradores como parámetros de valor, en lugar de pasarlos por referencia constante: lo que hiciste, para el segundo parámetro.

Sin embargo, en este ejemplo, debe devolver dos valores: el valor int analizado y el valor iterador nuevo / modificado; y dado que una función no puede tener dos códigos de retorno, codificar uno de los códigos de retorno como una referencia no constante es IMO normal.

Una alternativa sería codificarlo de esta manera:

//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)
{
}

En mi opinión, si desea hacer esto, el argumento debe ser un puntero al iterador que cambiará. No soy un gran admirador de los argumentos de referencia no constantes porque ocultan el hecho de que el parámetro pasado podría cambiar. Sé que hay muchos usuarios de C ++ que no están de acuerdo con mi opinión sobre esto, y eso está bien.

Sin embargo, en este caso es tan común que los iteradores sean tratados como argumentos de valor que creo que es una idea particularmente mala pasar iteradores por referencia no constante y modificar el iterador pasado. Simplemente va en contra de la forma idiomática en la que generalmente se usan los iteradores.

Dado que hay una excelente manera de hacer lo que quiere que no tenga este problema, creo que debería usarlo:

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

Ahora una persona que llama tendría que hacer:

int i = parse_integer(&p, end);

Y será obvio que el iterador se puede cambiar.

Por cierto, también me gusta la sugerencia de litb de devolver el nuevo iterador y colocar los valores analizados en una ubicación especificada por un iterador de salida.

En este contexto, creo que pasar un iterador por referencia es perfectamente sensato, siempre y cuando esté bien documentado.

Vale la pena señalar que su enfoque (pasar un iterador por referencia para realizar un seguimiento de dónde se encuentra cuando se tokeniza una secuencia) es exactamente el enfoque adoptado por boost :: tokenizer . En particular, consulte la definición del Concepto de función Tokenizer . En general, creo que boost :: tokenizer está bastante bien diseñado y bien pensado.

Creo que los algoritmos de la Biblioteca estándar pasan los iteradores por valor exclusivamente (alguien ahora publicará una excepción obvia a esto): este puede ser el origen de la idea. ¡Por supuesto, nada dice que su propio código debe parecerse a la Biblioteca estándar!

Al segundo parámetro de su declaración de función le falta la referencia, ¿no?

De todos modos, volviendo a su pregunta: No, nunca he leído nada que diga que no debe pasar iteradores por referencia. El problema con las referencias es que le permiten cambiar el objeto referenciado. En este caso, si va a cambiar el iterador, potencialmente está arruinando toda la secuencia más allá de ese punto, lo que hace que el procesamiento posterior sea imposible.

Solo una sugerencia: escriba sus parámetros con cuidado.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top