2009-05-09 18 views
33

He escrito un par de funciones con un prototipo de esta manera:¿Qué hay de malo en pasar el iterador de C++ por referencia?

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

La idea es que la persona que llama proporcionaría un rango de caracteres, y la función interpretaría los personajes como un valor entero y devolverlo, dejando comenzar en un pasado el último personaje 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 conjunto a 123 y p "señalador" en el espacio antes de foo.

Me han dicho (sin explicación) que está mal pasar un iterador por referencia. ¿Es mala forma? Si es así, ¿por qué?

Respuesta

29

No hay nada realmente mal, pero sin duda limitar el uso de la plantilla. No podrá simplemente poner un iterador devuelto por otra cosa o generado como v.begin(), ya que esos serán temporales.Siempre primero tendrá que hacer una copia local, que es una especie de repetitivo que no es muy 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 el número se escribe en:

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 inserter para poner los números analizados en un vector o un puntero para ponerlos 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)); 
+2

"No podrá simplemente poner un iterador devuelto por otra cosa o generado como v.begin(), ya que esos serán temporales." Para eso son las referencias de C++ 0x rvalue. :) – Zifre

+0

Me gusta la idea del iterador de salida es muy stl-esque – iain

+1

Otra alternativa (para los lectores tardíos): return an :: std :: pair , análogamente a :: std :: map :: insert does . – Aconcagua

3

En general:

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

Puede pasar una referencia const, pero por lo general los iteradores son lo suficientemente pequeños como para no dar ninguna ventaja sobre pasar por valor.

En su caso:

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

-1

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

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 al que se hace referencia. En este caso, si va a cambiar el iterador, potencialmente está arruinando toda la secuencia más allá de ese punto, imposibilitando así el procesamiento adicional.

Solo una sugerencia: escriba sus parámetros cuidadosamente.

+0

No, el segundo parámetro fue de valor porcentual a propósito, ya que no hay necesidad de pasarlo por referencia. Sí, pasar por referencia permite que la función cambie el parámetro. Esa fue la intención. No entiendo tu punto. Cambiar el iterador no puede "estropear la secuencia completa". Cambiar el iterador es diferente a cambiar los datos en el rango. Estos fueron 'const_iterators' después de todo. –

1

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.

2

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

En este ejemplo, sin embargo, debe devolver dos valores: el valor int analizado y el valor del 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 referencia no const es normal de IMO.

Una alternativa sería la de codificar que algo como esto:

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

Estaba pensando en devolver un par también, pero eso requiere algo de repetición en el código de la aplicación ... A menos que vaya por boost :: tie. – Reunanen

2

En mi opinión, si desea hacer esto, el argumento debe ser un puntero al repetidor que va a cambiar. No soy un gran admirador de los argumentos de referencia no const 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 al respecto, y eso está bien.

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

Dado que existe una gran manera de hacer lo que quiera que no tiene este problema, creo que se debe utilizar:

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 litb's suggestion de devolver el nuevo iterador y poner los valores analizados en una ubicación especificada por un iterador de salida.

1

En este contexto, creo que pasar un iterador por referencia es perfectamente razonable, siempre que 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 que se toma por boost::tokenizer. En particular, consulte la definición de TokenizerFunction Concept. En general, creo que boost :: tokenizer está bastante bien diseñado y bien pensado.

Cuestiones relacionadas