Domanda

Ho scritto alcune funzioni con un prototipo come questo:

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

L'idea è che il chiamante fornirebbe un intervallo di caratteri, e la funzione interpreterebbe i caratteri come un valore intero e lo restituirebbe, lasciando a inizio uno oltre l'ultimo carattere utilizzato. Ad esempio:

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

Questo lascerebbe i impostato su 123 e p " puntando " nello spazio prima di foo.

Da allora mi è stato detto (senza spiegazione) che è una forma sbagliata passare un iteratore per riferimento. È una cattiva forma? In tal caso, perché?

È stato utile?

Soluzione

Non c'è nulla di veramente sbagliato, ma certamente limiterà l'uso del modello. Non sarai in grado di inserire un iteratore restituito da qualcos'altro o generato come v.begin(), dal momento che saranno temporanei. Dovrai sempre prima fare una copia locale, che è una specie di boilerplate non molto bello da avere.

Un modo è sovraccaricarlo:

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

Un'altra opzione è quella di avere un iteratore di output in cui il numero verrà scritto in:

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

Avrai il valore di ritorno per restituire il nuovo iteratore di input. E puoi quindi utilizzare un iteratore inseritore per inserire i numeri analizzati in un vettore o un puntatore per inserirli direttamente in un numero intero o in una sua matrice se conosci già la quantità di numeri.

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

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

Altri suggerimenti

In generale:

Se si passa un riferimento non - const, il chiamante non sa se l'iteratore viene modificato.

Potresti passare un <=> riferimento, ma di solito gli iteratori sono abbastanza piccoli da non offrire alcun vantaggio rispetto al passaggio per valore.

Nel tuo caso:

Non credo che ci sia qualcosa di sbagliato in ciò che fai, tranne per il fatto che non è troppo standard per quanto riguarda l'utilizzo dell'iteratore.

Quando dicono " non passare per riferimento " forse è perché è più normale / idiomatico passare iteratori come parametri valore, invece di passarli per riferimento const: cosa che hai fatto, per il secondo parametro.

In questo esempio, tuttavia, è necessario restituire due valori: il valore int analizzato e il valore iteratore nuovo / modificato; e dato che una funzione non può avere due codici di ritorno, codificare uno dei codici di ritorno come riferimento non const è normale IMO.

Un'alternativa sarebbe codificarla in questo modo:

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

Secondo me, se vuoi farlo, l'argomento dovrebbe essere un puntatore all'iteratore che cambierai. Non sono un grande fan degli argomenti di riferimento non costanti perché nascondono il fatto che il parametro passato potrebbe cambiare. So che ci sono molti utenti C ++ che non sono d'accordo con la mia opinione su questo - e va bene.

Tuttavia, in questo caso è così comune per gli iteratori essere trattati come argomenti di valore che penso sia una cattiva idea passare gli iteratori con riferimento non const e modificare l'iteratore passato. Va semplicemente contro il modo idiomatico di solito vengono utilizzati gli iteratori.

Dal momento che esiste un ottimo modo per fare ciò che vuoi che non presenta questo problema, penso che dovresti usarlo:

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

Ora un chiamante dovrebbe fare:

int i = parse_integer(&p, end);

E sarà ovvio che l'iteratore può essere modificato.

A proposito, mi piace anche il suggerimento di litb di restituire il nuovo iteratore e mettere i valori analizzati in una posizione specificata da un iteratore di output.

In questo contesto, penso che passare un iteratore per riferimento sia perfettamente sensato, purché ben documentato.

Vale la pena notare che il tuo approccio (passando un iteratore per riferimento per tenere traccia di dove ti trovi quando tokenizzi uno stream) è esattamente l'approccio adottato da boost :: tokenizer . In particolare, vedere la definizione del TokenizerFunction Concept . Nel complesso, trovo boost :: tokenizer per essere abbastanza ben progettato e ben pensato.

Penso che gli algoritmi della Libreria standard superino esclusivamente gli iteratori (qualcuno ora pubblicherà un'ovvia eccezione a questo) - questa potrebbe essere l'origine dell'idea. Ovviamente, nulla dice che il tuo codice deve assomigliare alla Libreria standard!

Nel secondo parametro della dichiarazione di funzione manca il riferimento, vero?

In ogni caso, tornando alla tua domanda: No, non ho mai letto nulla che dice che non dovresti passare iteratori per riferimento. Il problema con i riferimenti è che consentono di modificare l'oggetto a cui si fa riferimento. In questo caso, se si desidera modificare l'iteratore, si sta potenzialmente rovinando l'intera sequenza oltre quel punto, rendendo impossibile un'ulteriore elaborazione.

Un solo suggerimento: digita attentamente i tuoi parametri.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top