Wyjątki w konstruktorze

Wyjątki są najprostszą i najlepszą możliwością zgłoszenia błędów zaistniałych w konstruktorze, przez których tworzenie obiektu zakończyło się porażką.  Bez sytuacji wyjątkowych nie ma bezpośredniego prostego mechanizmu  zgłoszenia zaistniałej sytuacji, ponieważ konstruktor nie przekazuje żadnego typu i wartości, która mogłaby przekazać informacje o zaistniałym błędzie.

Oczywiście można obejść ten problem poprzez zastosowanie dodatkowej zmiennej lokalnej do określania stanu obiektu i sprawdzać stan po utworzeniu obiektu lub wykorzystać nielokalną, umowną zmienną, która "powie" nam, że tworzenie zakończyło się niepowodzeniem.

Rzucanie wyjątków w konstruktorze niesie pewne problemy, poczynając od wycieków pamięci i zasobów, do niespójności obiektów.

Problem nie istnieje dla obiektów zawierających podobiekty i rzucanie wyjątków z tych podobiektów w konstruktorze. Mechanizm obsługi sytuacji wyjątkowych w języku C++ gwarantuje nam, że podobiekty całkowicie skonstruowane będą zniszczone, podobiekty jeszcze nie całkiem skonstruowane – nie.

Inna sprawa, jeśli posiadamy składowe wskaźnikowe i w ctor tworzymy jakieś obiekty a w dtor je usuwamy (to samo dotyczy dowolnych zasobów). Jeśli podobiekty te mogą rzucić wyjątkiem w czasie ich konstruowania, powinniśmy nasz konstruktor odpowiednio przed tym zabezpieczyć, aby nie dopuścić do wycieków zasobów lub naruszenia spójności obiektu.

Ale o tym nie ma co opowiadać, bo każdy o tym wie ;)

Chciałem tylko wspomnieć o ciekawej, acz rzadko spotykanej konstrukcji bloku try-catch dla list inicjalizacyjnych konstruktora, która rozciąga się na cale ciało i listę inicjalizacyjną:

class Bar {
public:
	Bar(Foo foo1, Foo foo2)
	try : m_foo1(foo1), m_foo1(foo2) {
		// cialo konstruktora
		}
	catch (...) {
		throw;
	}
 
	~Bar() {}
 
private:
	Foo m_foo1;
	Foo m_foo2;;
};

Konstruktory klasy Foo mogą rzucić wyjątkiem w czasie tworzenia, bądz kopiowania.

W kodzie obsługi wyjątku nie możemy odwoływać się do żadnych elementów obiektu Bar, ponieważ on nie istnieje. Aczkolwiek w tym przypadku nie jest to dla nas wielkim problemem, bo obiekty klasy Foo są tworzona na stosie, dlatego jak przedstawiono w punkcie o obiektach prostych, zabezpieczeniem zajmą się mechanizmy języka, gwarantujące spójność i zniszczenie utworzonych podobiektów.

Innym sposobem rozwiązania problemu rzucania wyjątków w konstruktorze przy składowych wskaźnikowych i tworzeniu podobiektów jest skorzystanie z idiomu RAII.

Wtedy składową staje się inny obiekt – zarządca zasobu - prosta "nakładka" na wskaźnik, a przez to problem zasobów możemy znów pozostawić mechanizmom języka, które zadbają o zniszczenie obiektu.

Najprościej wykorzystać tutaj auto_ptr z biblioteki standardowej, ale należy robić to z rozwagą, aby nie popaść w jeszcze większe tarapaty.

Ok., już nie marudzę ;)

Więcej głębszych informacji można znaleźć w GotW #66: Constructor Failures.

Dodaj komentarz

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