Stream iterators

Pisałem o numeric_limits jako zapomnianej części biblioteki numerycznej, dziś do tego „worka” mogę dorzucić iteratory strumieni jako rzadko używane „twory”. Niektórych może zdziwić ten fakt, że strumienie także posiadają iteratory, a ich wykorzystanie może bardzo nam pomoc i uprościć kod.

Na początek najlepiej zapoznać się z tym co mówi dokumentacja na temat istream_iterator i ostream_iterator z nagłówka iterator.

Często iteratory te są wykorzystywane w różnych przykładach innych algorytmów do szybkiego pobierania danych z strumienia wejściowego i wyświetlania na wyjściowym, w produkcyjnym oprogramowaniu rzadko je można spotkać.

Nasz prosty programik będzie pobierał dane z strumienia wejściowego i umieszczał je w kontenerze, a następnie na tych danych będziemy wykonywać jakieś operacje, po czym na koniec „wypluwać” wynik na wyjściowy strumień.

Z pomocą iteratorów strumieni i algorytmów biblioteki standardowej, łatwo możemy zaimplementować obsługę strumieni – pobieranie/wysyłanie danych z/do wektora. W naszym przypadku moglibyśmy wykorzystać algorytm std::copy (z nagłówka algorithm) i funkcje te przybrałyby bardzo prostą postać:

inline void get_data(std::istream& from, std::vector<std::string>& to) {
 
	std::copy(std::istream_iterator<std::string>(from),
		std::istream_iterator<std::string>(),
		std::back_inserter(to));
 
}
 
inline void put_data(std::vector<std::string>& from, std::ostream& to) {
 
	std::copy(from.begin(), from.end(),
		std::ostream_iterator<std::string>(to, "\n"));
 
}

Nasz programik sortujący wprowadzone słowa:

int main() {
	std::vector<std::string> vec;
 
	get_data(std::cin, vec);
 
	std::sort(vec.begin(), vec.end());
	std::cout << std::endl << "Sorted:" << std::endl;
 
	put_data(vec, std::cout);
 
	return 0;
}

Nic nie stoi na przeszkodzie, aby podobnie operować na strumieniach plikowych – fstream.

Warto jeszcze wspomnieć, że istnieją podobne iteraotry dla buforów strumieniowych – istreambuf_iterator i ostreambuf_iterator.

Bardzo przydatne może się okazać narzędzie służące do szybkiego zapisywania do strumienia wyjściowym zawartości kontenera, tak jak chociażby w wyżej zdefiniowanej funkcji put_data.

Małą niedogodnością jest tutaj wypisywanie niepotrzebnego na końcu delimitera, ale można się tego łatwo pozbyć.

Poniżej prosta funkcja do „wypluwania” zawartości dowolnego kontenera.

template<typename ContainerType>
inline void put_data(std::ostream& os, ContainerType& con, const std::string& delim) {
 
	typename ContainerType::const_iterator last = con.end();
	std::advance(last, -1);
 
	if (con.size() > 1)
		std::copy(con.begin(), last,
			std::ostream_iterator<typename ContainerType::value_type>(os, delim.c_str()));
 
	if (con.size() > 0)
		os << *last;
 
}

Funkcja działa bezproblemowo dla kontenerów przechowujących proste typy, a dokładniej takie, które posiadają zdefiniowany operator << dla strumienia wyjściowego z naszym typem, dla pozostałych typów, zdefiniowanych przez użytkownika, musimy zadbać o takowy operator. Podobnie, aby funkcja współdziałała z kontenerów przechowujących pary powiązanych ze sobą kluczy i wartości, tak jak std::map niezbędne jest dodanie dodatkowego mechanizmu do prezentacji takich danych, najlepiej chyba w postaci:

klucz - wartość

Mechanizmem tym jest wspomniany operator << dla danej pary:

template<class T, class U>
std::ostream& operator<<(std::ostream& os, const std::pair<T, U>& p) {
	return os << p.first << " - " << p.second;
}

Dla poprawnego działania, operator ten powinien zostać zdefiniowany w przestrzeni nazw std.

I na koniec prosty programik prezentujący i testujący działanie naszego mechanizmu.

Dla prostoty zdefiniujmy sobie dodatkowe operatory << strumienia wyjściowego z używanymi kontenerami:

std::ostream& operator<<(std::ostream& os, const std::vector<int>& vec) {
	put_data(os, vec, ", ");
	return os;
}
 
std::ostream& operator<<(std::ostream& os, const std::map<int, int>& vec) {
	put_data(os, vec, ", ");
	return os;
}

A program przyjmie prostą postać:

int main() {
 
	std::vector<int> vec(10);
 
	int array[] = { 1, 2, 3, 4, 5 };
	vec.assign(array, array + sizeof(array) / sizeof(int));
	std::cout << "vector sized at " << vec.size() << ": " << vec << std::endl;
 
	vec.resize(1);
	std::cout << "vector sized at " << vec.size() << ": " << vec << std::endl;
 
	vec.clear();
	std::cout << "vector sized at " << vec.size() << ": " << vec << std::endl;
 
 
	std::map<int, int> map;
 
	map[0] = 1;
	map[1] = 2;
	map[2] = 3;
	std::cout << "map sized at " << map.size() << ": " << map << std::endl;
 
	return 0;
 
}

Myślę, że łatwo można zauważyć to, że iteratory strumieni, w niektórych sytuacjach mogą być bardzo pomocne i nieraz mogą ułatwić nam pracę.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *