Question

J'ai écrit quelques fonctions avec un prototype comme celui-ci:

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

L’idée est que l’appelant fournisse une plage de caractères et que la fonction interprète les caractères comme une valeur entière et les renvoie, en laissant commencer au-delà du dernier caractère utilisé. Par exemple:

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

Cela laisserait i la valeur 123 et p & "pointant &"; à l'espace avant foo.

On m'a depuis dit (sans explication) qu'il est mauvais de passer d'un itérateur par référence. Est-ce une mauvaise forme? Si oui, pourquoi?

Était-ce utile?

La solution

Il n’ya rien de vraiment faux, mais cela limitera certainement l’utilisation du modèle. Vous ne pourrez pas simplement mettre un itérateur retourné par quelque chose d'autre ou généré comme v.begin(), car ce seront des temporaires. Vous devrez toujours d'abord faire une copie locale, ce qui est une sorte de passe-partout pas vraiment agréable à avoir.

Une solution consiste à le surcharger:

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

Une autre option consiste à créer un itérateur en sortie dans lequel le numéro sera écrit:

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

Vous aurez la valeur de retour pour renvoyer le nouvel itérateur d’entrée. Et vous pouvez ensuite utiliser un itérateur insérateur pour placer les nombres analysés dans un vecteur ou un pointeur pour les mettre directement dans un entier ou un tableau de ceux-ci si vous connaissez déjà le nombre de nombres.

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

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

Autres conseils

En général:

Si vous transmettez une référence non - const, l'appelant ne sait pas si l'itérateur est en cours de modification.

Vous pouvez passer une <=> référence, mais les itérateurs sont généralement assez petits pour ne pas donner d’avantage sur le passage par valeur.

Dans votre cas:

Je ne pense pas qu'il y ait quelque chose qui cloche dans ce que vous faites, sauf que ce n'est pas trop standard en ce qui concerne l'utilisation des itérateurs.

Quand ils disent & "ne pas passer par référence &"; c’est peut-être parce qu’il est plus normal / idiomatique de passer les itérateurs en tant que paramètres de valeur, au lieu de les transmettre par référence const: ce que vous avez fait pour le second paramètre.

Dans cet exemple, vous devez toutefois renvoyer deux valeurs: la valeur int analysée et la valeur itérateur nouveau / modifié; et étant donné qu’une fonction ne peut pas avoir deux codes retour, coder l’un des codes retour comme référence non-const est IMO normal.

Une alternative serait de coder quelque chose comme ceci:

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

À mon avis, si vous voulez faire cela, l'argument devrait être un pointeur sur l'itérateur que vous allez modifier. Je ne suis pas un grand fan des arguments de référence non const, car ils cachent le fait que le paramètre passé peut changer. Je sais que de nombreux utilisateurs de C ++ sont en désaccord avec mon opinion à ce sujet - et c'est bien.

Cependant, dans ce cas, il est donc commun pour que les itérateurs soient traités comme des arguments de valeur, ce qui est une très mauvaise idée de passer des itérateurs par référence non const et de modifier celui-ci. Cela va à l’encontre de la façon idiomatique d’utiliser les itérateurs.

Puisqu'il existe un excellent moyen de faire ce que vous voulez sans ce problème, je pense que vous devriez l'utiliser:

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

Maintenant, un appelant devrait faire:

int i = parse_integer(&p, end);

Et il sera évident que l'itérateur peut être changé.

Au fait, j'aime aussi les suggestion de litb de renvoyer le nouvel itérateur et de placer les valeurs analysées dans un emplacement spécifié par un itérateur en sortie.

Dans ce contexte, j'estime qu'il est parfaitement judicieux de passer d'un itérateur par référence, à condition que ce soit bien documenté.

Il est intéressant de noter que votre approche (passage d'un itérateur par référence pour savoir où vous en êtes lors de la création d'un flux) est exactement celle adoptée par boost :: tokenizer . En particulier, voir la définition du Concept TokenizerFunction . Globalement, je trouve que boost :: tokenizer est très bien conçu et bien pensé.

Je pense que les algorithmes de la bibliothèque standard transmettent les itérateurs exclusivement par valeur (quelqu'un va maintenant afficher une exception évidente à cela) - cela pourrait être à l'origine de l'idée. Bien entendu, rien ne dit que votre propre code doit ressembler à la bibliothèque standard!

Le deuxième paramètre de votre déclaration de fonction ne contient pas la référence, n'est-ce pas?

Quoi qu’il en soit, revenons à votre question: non, je n’ai jamais lu quoi que ce soit qui indique que vous ne devriez pas utiliser les itérateurs par référence. Le problème avec les références est qu'elles vous permettent de modifier l'objet référencé. Dans ce cas, si vous souhaitez modifier l'itérateur, vous risquez de tout gâcher au-delà de la séquence, rendant tout traitement ultérieur impossible.

Juste une suggestion: tapez vos paramètres avec soin.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top