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ê?

Foi útil?

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

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top