O que há de errado com o passar do C ++ iterator por referência?
-
20-08-2019 - |
Pergunta
Eu escrevi algumas funções com um protótipo como este:
template <typename input_iterator>
int parse_integer(input_iterator &begin, input_iterator end);
A idéia é que o chamador iria fornecer uma gama de caracteres e a função iria interpretar os caracteres como um valor inteiro e devolvê-lo, deixando começar em um após o último utilizado personagem. Por exemplo:
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);
Isto deixaria conjunto i
para 123 e p
"apontando" no espaço antes foo
.
Eu já foi dito (sem explicação) que é má forma para passar um iterador por referência. É má forma? Se sim, porquê?
Solução
Não há nada realmente errado, mas certamente vai limitar o uso do modelo. Você não será capaz de simplesmente colocar um iterador retornado por outra coisa ou gerado como v.begin()
, pois esses serão temporários. Você vai sempre em primeiro lugar tem que fazer uma cópia local, que é uma espécie de clichê não muito bom ter.
Uma maneira é sobrecarregá-lo:
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);
}
Outra opção é ter um iterador de saída onde o número será escrito em:
template<typename input_iterator, typename output_iterator>
input_iterator parse_integer(input_iterator begin, input_iterator end,
output_iterator out);
Você terá o valor de retorno para retornar o novo iterador de entrada. E você poderia, então, usar uma inserção iterador para colocar os números analisado em um vetor ou um ponteiro para colocá-los diretamente em um inteiro ou uma matriz do mesmo se você já sabe a quantidade de números.
int i;
b = parse_integer(b, end, &i);
std::vector<int> numbers;
b = parse_integer(b, end, std::back_inserter(numbers));
Outras dicas
Em geral:
Se você passar uma referência não-const
, o chamador não sabe se o iterador está sendo modificado.
Você poderia passar uma referência const
, mas geralmente iteradores são pequenos o suficiente para que ele não dá nenhuma vantagem sobre passando por valor.
No seu caso:
Eu não acho que haja algo de errado com o que você faz, exceto que ele não é muito padrão-esque sobre iterador uso.
Quando eles dizem "não passar por referência" Talvez seja porque é mais normal / idiomática para passar iterators como parâmetros de valor, em vez de passá-los por referência const:. Que você fez, para o segundo parâmetro
Neste exemplo, contudo, você precisa de retornar dois valores: o valor int analisado e, a nova / modificada iterador valor; e tendo em conta que uma função não se pode ter dois códigos de retorno, que codifica um dos códigos de retorno como uma referência não-const é IMO normal.
Uma alternativa seria a de código que algo como isto:
//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)
{
}
Na minha opinião, se você quer fazer isso o argumento deve ser um ponteiro para o iterador você vai estar mudando. Eu não sou um grande fã de argumentos de referência não-const, porque eles escondem o fato de que o parâmetro passado pode mudar. Eu sei que há um monte de usuários C ++ que não concordam com a minha opinião sobre isso -. E isso é bom
No entanto, neste caso, de para comum para iterators de ser tratado como argumentos valor que eu acho que é uma particularmente má idéia para passar iterators por referência não-const e modificar o iterador passado. Ele só vai contra a forma idiomática iteradores são normalmente utilizados.
Uma vez que existe uma grande maneira de fazer o que quiser que não tem esse problema, eu acho que você deve usá-lo:
template <typename input_iterator>
int parse_integer(input_iterator* begin, input_iterator end);
Agora, um chamador teria que fazer:
int i = parse_integer(&p, end);
E vai ser óbvio que o iterador pode ser alterado.
A propósito, eu também gosto sugestão do litb de retornar a nova iteração e colocando os valores analisado em um local especificado por um iterador de saída.
Neste contexto, acho que passar um iterador por referência é perfeitamente sensato, desde que ele é bem documentado.
É importante notar que a sua abordagem (passando um iterador por referência para manter o controle de onde você é quando tokenizing um fluxo) é exatamente a abordagem que é tomado por boost :: tokenizer . Em particular, ver a definição do TokenizerFunction Conceito . No geral, eu acho boost :: tokenizer que ser muito bem concebido e bem pensado.
Eu acho que os algoritmos de biblioteca padrão passar iterators por valor exclusivamente (alguém vai agora postar uma exceção óbvia para isso) - esta pode ser a origem da ideia. Claro, nada diz que o seu próprio código deve se parecer com a biblioteca padrão!
segundo parâmetro da sua declaração de função está em falta a referência, não é?
Enfim, de volta à sua pergunta: Não, eu nunca li nada que diz que você não deve passar iterators por referência. O problema com referências é que eles permitem que você altere o objeto referenciado. Neste caso, se você está a alterar o iterador, que são potencialmente estragar toda a seqüência além desse ponto tornando assim ainda mais o processamento impossível.
Apenas uma sugestão:. Digitar seus parâmetros cuidadosamente