2009-05-09 19 views
33

Pisałem kilka funkcji z prototypem takiego:Co jest nie tak z przekazywaniem iteratora C++ przez odwołanie?

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

Chodzi o to, że rozmówca dostarczy szereg znaków, a funkcja będzie interpretować znaki jako wartość całkowitą i zwraca go, pozostawiając początek z przeszłości obok ostatnio używanej postaci. Na przykład:

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

To pozostawi i zestaw do 123 i p "wskazującym" na przestrzeń przed foo.

Od tego czasu powiedziano mi (bez wyjaśnienia), że przekazywanie iteratora przez podanie jest złym pomysłem. Czy to zła forma? Jeśli tak, dlaczego?

Odpowiedz

29

Nie ma nic naprawdę złego, ale z pewnością ograniczyć korzystanie z szablonu. Nie będzie można po prostu wstawić iteratora zwróconego przez coś innego lub wygenerowanego jak v.begin(), ponieważ będą to tymczasowe.Zawsze będziesz musiał najpierw wykonać lokalną kopię, która jest czymś w rodzaju niezbyt przyjemnej.

Jednym ze sposobów jest to przeciążenie:

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

Innym rozwiązaniem jest mieć iterator wyjściowe, w których liczba zostanie napisany w:

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

Będziesz mieć wartość zwracaną do zwrotu nowy iterator wejścia. Następnie można użyć iteratora wprowadzającego, aby umieścić sparsowane liczby w wektorze lub wskaźniku, aby umieścić je bezpośrednio w liczbie całkowitej lub w ich tablicy, jeśli już znasz liczbę liczb.

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

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

"Nie będzie można po prostu wstawić iteratora przez coś innego lub wygenerowanego jak v.begin(), ponieważ będą to tymczasowe." Do tego przeznaczone są wartości rvalue w C++ 0x. :) – Zifre

+0

Podobał mi się pomysł iteratora wyjściowego jest bardzo stl-esque – iain

+1

Inna alternatywa (dla późnych czytelników): zwracanie :: std :: pair , analogicznie jak :: std :: map :: insert does . – Aconcagua

3

Ogólnie:

Jeśli zdasz niebędącą const odniesienia, rozmówca nie wie, jeśli iterator jest modyfikowany.

Można podać odniesienie const, ale zwykle iteratory są na tyle małe, że nie dają żadnej przewagi nad przekazywaniem wartości.

W twoim przypadku:

Nie sądzę, nie ma nic złego w tym co robisz, z wyjątkiem, że to nie jest zbyt standardowy-owskiej dotyczące użycia iteratora.

-1

Drugi parametr deklaracji funkcji nie zawiera odniesienia, czyż nie?

Wracając do pytania: Nie, nigdy nie przeczytałem niczego, co mówi, że nie powinieneś podawać iteratorów przez odwołanie. Problem z odniesieniami polega na tym, że umożliwiają one zmianę obiektu, do którego się odwołuje. W takim przypadku, jeśli chcesz zmienić iterator, potencjalnie skręcasz całą sekwencję poza ten punkt, uniemożliwiając dalsze przetwarzanie.

Jedna sugestia: ostrożnie wpisz parametry.

+0

Nie, drugi parametr był celowo wartością dodaną, ponieważ nie ma potrzeby przekazywania go przez odniesienie. Tak, przekazywanie przez odniesienie pozwala funkcji na zmianę parametru. Taka była intencja. Nie rozumiem twojego punktu widzenia. Zmiana iteratora nie może "zepsuć całej sekwencji". Zmiana iteratora jest inna niż zmiana danych w zakresie. W końcu były to "const_iterators". –

1

Myślę, że algorytmy ze standardowej biblioteki przekazują iteratory wyłącznie według wartości (ktoś będzie teraz wysyłać oczywisty wyjątek) - może to być źródłem pomysłu. Oczywiście nic nie mówi, że twój własny kod musi wyglądać jak Biblioteka Standardowa!

2

Kiedy mówią "nie przechodź przez referencję" może to być spowodowane tym, że bardziej normalne/idiomatyczne jest przekazywanie iteratorów jako parametrów wartości, zamiast przekazywania ich przez odniesienie do stałych: co zrobiłeś dla drugiego parametru.

W tym przykładzie należy jednak zwrócić dwie wartości: przeanalizowaną wartość int i nową/zmodyfikowaną wartość iteratora; i biorąc pod uwagę, że funkcja nie może mieć dwóch kodów powrotnych, kodowanie jednego z kodów powrotu jako niestałego odniesienia jest normalne dla IMO.

Alternatywą byłoby zakodować to mniej więcej tak:

//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

Myślałem również o zwrocie pary, ale to wymaga pewnego schematu na kodzie aplikacji ... Chyba że pójdziesz na boost :: tie. – Reunanen

2

Moim zdaniem, jeśli chcesz to zrobić, argument powinien być wskaźnikiem do iteratora, który będziesz zmieniać. Nie jestem wielkim fanem argumentów odwołujących się do stałych, ponieważ ukrywają one fakt, że przekazany parametr może się zmienić. Wiem, że jest wielu użytkowników C++, którzy nie zgadzają się z moją opinią na ten temat - i to jest w porządku.

Jednak w tym przypadku jest to , więc wspólne dla iteratorów, które mają być traktowane jako argumenty wartości, które uważam za szczególnie zły pomysł, aby przekazać iteratory przez referencje bez stałych i zmodyfikować przekazany iterator. To po prostu idzie wbrew idiomatycznemu sposobowi, w jaki zwykle używane są iteratory.

Ponieważ jest to świetny sposób, aby zrobić to, co chcesz, że nie ma tego problemu, myślę, że należy go używać:

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

Teraz rozmówca musiałby zrobić:

int i = parse_integer(&p, end); 

I będzie oczywiste, że iterator można zmienić.

Nawiasem mówiąc, podoba mi się także litb's suggestion od zwracania nowego iteratora i umieszczania przeanalizowanych wartości w miejscu określonym przez iterator wyjścia.

1

W tym kontekście myślę, że przekazywanie iteratora przez odniesienie jest całkowicie sensowne, o ile jest dobrze udokumentowane.

Warto zauważyć, że Twoje podejście (przekazywanie iteratora przez odniesienie do tego, gdzie jesteś przy okazji tokenizacji strumienia) jest dokładnie tym podejściem, które jest podejmowane przez boost::tokenizer. W szczególności zobacz definicję TokenizerFunction Concept. Ogólnie uważam, że boost :: tokenizer jest całkiem dobrze zaprojektowany i dobrze przemyślany.