Użycie wskaźnika na elementy vectora

Tagi: błądbłb, błędy, C++, konkord, programowanie, STL, vector,
Kategoria:

Podczas wprowadzenia zmian i testowania programu do nauki słówek wykryłem błąd, który nie był zwykłą literówką, lecz błędem w strukturze programu, pojawiającym się raz na ileś. Taki błąd jest najgorszy, bo trudno odgadnąć, dlaczego to program się wyspuje, jest trudny do uchwycenia. Z tym błędem wiązało się też, że rozwiązując go, musiałem stanąć przed dylematem, które rozwiązanie wybrać. Zaprezentuję przykład, gdzie będzie ten sam błąd

#include 
#include 
using namespace std;
int main() {
	vector elements;
	int **wsk_to_elements = new int*[10000];
	for(int i = 0; i < 10000; i++) {
		elements.push_back(i);
		wsk_to_elements[i] = &elements[i];
	}
	for(int i = 0; i < 10000; i++) {
		elements.push_back(i);
	}
	vector elements2;
	for(int i = 0; i < 100; i++) {
		elements2.push_back(i+10000);
	}
	for(int i = 0; i < 10000; i++) {
		cout << *wsk_to_elements[i] << endl;
	}
	return 0;
}
Niby wszystko w porządku, ale… po skompilowaniu i uruchomienu raczej zobaczymy, że 100 pierwszych wskaźników z tablicy wsk_to_elements wskazuje nam na jakieś elementy, których wartość jest większa od 10000, czyli chyba elementy vectora elements2. Dlaczego? Vector to taki obiekt, który przechowuje nam elementy, pozwala nam dodawać i usuwać elementy w każdej chwili, ale też stara się zachować ciągłość w pamięci, dlatego gdy przy dodaniu 1000 elementu vector widzi, że na nie ma już miejsca na prawo, to przenosi wszystkie elementy do takiego miejsca w pamięci, gdzie będzie mógł dodać ten 1000 element. Krótko mówiąc, nie gwarantuje nam, że elementy będą cały czas w tym samym miejscu w pamięci. Można się przed tym zabezpieczyć rezerwując miejsce na x elementów, dając mu x w konstuktorze, ale przecież nie zawsze wiemy, jaki rozmiar nam będzie potrzebny. Dlatego użycie wskaźników na elementy vectora jest samobójstwem.

I właśnie taki błąd miałem w programie i miałem do wyboru kilka możliwości rozwiązania problemu: I. Posłużyć się iteratorami, ale w praktyce wyglądałoby to tak:

class SingleWord {
	//…
	vector::iterator something;
	//…
}
class Kurs {
	//…
	vector singleWords;
	//…
Trochę głupio uzależniać klasę SingleWord, która ma być niezależna od klas ją wykorzystujących. W porównaniu do drugiego rozwiązania zachowałbym taki sam czas dostępu do elementów. I rozwiązanie nie jest rozwiązaniem, bo ma te same wady, co rozwiązanie oparte na wskaźnikach. Rozwiązaniem podobnym do tego opartego na iteratorach byłoby:
class SingleWord {
	//…
	unsigned int something; //numer słowa w vectorze
	//…
}
class Kurs {
	//…
	vector singleWords;
	//…
To rozwiązanie ma jedną zasadniczę wadę: klasa SingleWord jest zależna od klasy ją wykorzystującej Kurs. II. W samym vectorze posłużyć się wskaźnikami, wyglądałoby to tak:
class SingleWord {
	//…
	SingleWord *something;
	//…
}
class Kurs {
	//…
	vector singleWords;
}
W tym rozwiązaniu klasa SingleWord zachowuje swoją niezależność, ale jest trochę wolniejszy dostęp do elementów z klasy Kurs, bo odbywa się przez wskaźniki. To, że w vectorze są wskaźniki, ma jeszcze inną zaletę, gdy vector będzie zmieniał swoje miejsce w pamięci, nie będzie kopiował ponad 100 bitowego elementu klasy SingleWord, lecz 64 bitowe wskaźniki. Oczywiście nie należy zapomnieć, że tym przypadku musimy samodzielnie zwalniać pamięć, vector.erase() usunie tylko wskaźniki, elementy, na które wskazywały zostaną.

Wybrałem II rozwiązanie, lecz zaskutkowało to tym, że musiałem zmieniać działanie wielu funkcji. Przy okazji tych zmian, stwierdziłem, że gdy chcę odmówić prawa do zmieniania elementu, lepiej zwrócić "wskaźnik na stały element" niż "element", program będzie szybciej działał, ponieważ wskaźnik jest tylko 64 bitowy. Zmieniłem… lecz mocno się zdziwiłem, bo funkcja wygląda tak:

SingleWord const* Kurs::getSingleWord() {
	SingleWord const* something;	
	//…
	return something;
}
, a program skompilował się(a nie powinien) z przypisaniem:
Kurs kurs;
//…
SingleWord sword = kurs.getSingleWord();
//…
Zgłosiem buga na Debianie dla pakietu g++-4.4. Bug nie jest taki straszny, lecz z pewnością utrudnia wykrywanie błędów w programie.

Zobacz komentarze

Pobieranie dużej ilości danych w jednej lini

Tagi: białe znaki, C++, cin, funkcje, programowanie, scanf, standardowe wejście, STL, wczytywanie danych,
Kategoria:

Niestety ja się za późno o tym dowiedziałem i m.in. przez to uzyskałem słaby wynik w II etapie OIG. Nie wiedziałem jak pobrać np. 100 zmiennych podanych w jednej lini, przez funkcje scanf jest to nieefektywne, pobranie stringa a później rozbijanie go na liczby też nie jest efektywne. Więc zacząłem szperać po necie i znalazłem takie info, że jak będę chciał pobrać przez cin pobrać "ala ma kota" to pobierane jest tylko "ala" a reszta pozostaje do późniejszego odczytania. To znaczy, że jakbym miał program:
#include #include using namespace std; main() { string _1; string _2; string _3; cin >>_1; cin >>_2; cin >>_3; return 0; }
To wpisując raz "ala ma kota" to "ala" byłaby w _1, "ma" w _2, "kota" w _3(a ja myślałem, że wszystko po pierwszym białym znaku jest kasowane). Teraz pobranie kilku wyrazów, liczb podanych w jednej lini odzdzielonych od siebie białym znakiem(np. spacja, "/t") jest dla mnie proste.

Zobacz komentarze