Co się stanie, gdy konstruktor globalnego obiektu statycznego rzuci
wyjątek? Czy da się jakoś przechwycić taki wyjątek?
Jeśli nie, to jak inaczej można zasygnalizować w programie,
że nie udało się poprawnie utworzyć obiektu, który był
singletonem?
--
SasQ
Dlatego taki singleton robię tak, aby ryzykowna część tworzenia była
później, np. przy pierwszym użyciu. Wg mnie to dopiero rasowy singleton.
W części statycznej to, co zawsze się uda (null pointery, jakiś string itd)
> Dlatego taki singleton robię tak, aby ryzykowna część tworzenia
> była później, np. przy pierwszym użyciu.
A jeśli sposobów pierwszego użycia będzie dużo?
Przecież nie będę w każdym się upewniał, czy obiekt już
został stworzony :| No chyba że masz na to jakiś sprytny trick ;)
OK, może powiem co konkretnie chcę osiągnąć.
Chciałem napisać klasę Ekran do obsługi ekranu graficznego
[taki wrapperek, który sobie mógłbym implementować przy użyciu
różnych innych bibliotek graficznych, typu np. DirectX czy SDL].
Obiekt tej klasy ma być utożsamiany z ekranem monitora.
Ekran monitora istnieje od samego początku działania programu
[a nawet przed nim ;J], i jest tylko jeden dla całego programu.
Dlatego pomyślałem, by zrealizować go jako singleton - globalny
obiekt przechowywany statycznie w klasie, w taki sposób, by
nie dało się utworzyć sobie ręcznie dodatkowych obiektów tej
klasy [konstruktor prywatny] i żeby dostęp do niego był
kontrolowany przez klasę Ekran i przez nią się odbywał.
Użytkownik tej klasy widziałby to tak, że przychodzi "na gotowe",
czyli na początku funkcji main() może być pewny, że obiekt
ekranu istniał tam od zawsze i jest od razu dostępny. W taki
sam sposób jak dostępne są od razu obiekty strumieni I/O
[std::cin, std::cout, std::err itp.].
Przygotowywanie takiego ekranu do pracy może się nie udać.
Np. może nie zostać utworzone okno, w którym on działa.
Może się nie udać inicjalizacja SDL, lub utworzenie obiektu
głównego Direct3D, czy co tam on będzie opakowywał ;P
Może się nie udać inicjalizacja ekranu w bieżącym trybie
graficznym i pobranie informacji o jego aktualnym trybie.
W takich sytuacjach chciałoby się rzucić wyjątkiem, bo
wszystko jest realizowane w konstruktorze tego statycznego
obiektu w klasie przy jego inicjalizacji.
Problem pojawił się właśnie z tymi wyjątkami. Na poniższe
pytanie nikt mi w zasadzie narazie nie odpowiedział:
>> Co się stanie, gdy konstruktor globalnego obiektu
>> statycznego rzuci wyjątek? Czy da się jakoś przechwycić
>> taki wyjątek?
Myślę nad jakimś takim rozwiązaniem, żeby sama klasa Ekran
łapała gdzieś ten wyjątek i obsługiwała wyświetlając np.
MessageBoxa z informacją, że nie udało się zainicjalizować
ekranu, czy co tam go będzie boleć...
Tylko nie wiem czy takie reakcje leżą w gestii biblioteki,
która ma służyć programowi głównemu, a nie dyktować własne
warunki i decydować o wyświetleniu komunikatu :P Nie wiem
jak to rozwiązać, by program główny miał możliwość zostać
powiadomiony, że nie udało się przygotować ekranu do pracy
przed rozpoczęciem main(), by mógł zareagować na tą sytuację
po swojemu. O to właśnie mniej więcej pytałem tu:
>> Jeśli nie, to jak inaczej można zasygnalizować w programie,
>> że nie udało się poprawnie utworzyć obiektu, który był
>> singletonem?
Heh... ciężki orzech do zgryzienia :P
--
SasQ
Ja bym unikał ambitnego kodu przed main. (Inicjacja bliootek standardowych,
zmiennych środowiska itd itd)
Singletona postrzegam (szkołą javowska) że najważniejszy kod jest
wykonywany w metodzie
static getMySingleton()
a nie w konstruktorze instancji statycznej (szkoła C++) - tu najwyżej
zerowanie handlerów, pointerów itd)
a ten MySingleton * MySingleton::getMySingleton() jest wołany już z main w
jego początkowych liniach
z innej strony, stary dobry kod powrotu C też wolno ci zapamiętac i
skorzystać z niego. exception to nie przymus.
> Chciałem napisać klasę Ekran do obsługi ekranu graficznego
> [taki wrapperek, który sobie mógłbym implementować przy użyciu
> różnych innych bibliotek graficznych, typu np. DirectX czy SDL].
> Obiekt tej klasy ma być utożsamiany z ekranem monitora.
> Ekran monitora istnieje od samego początku działania programu
Ale nie graficzny. Wyobraź sobie program, który w zależności od
sposobu uruchomienia korzysta z grafiki lub nie. Jeśli nie korzysta,
to może być uruchomiony nawet tam, gdzie grafiki nie ma (np. sesja
ssh bez tunelowania X-ów).
--
__("< Marcin Kowalczyk
\__/ qrc...@knm.org.pl
^^ http://qrnik.knm.org.pl/~qrczak/
--
SasQ
> Jak to jest robione w przypadku obiektów globalnych,
> takich jak std::cin czy std::cout?
> Przecież one też
> są obiektami tworzonymi jeszcze przed funkcją main()
> i nie są to chyba obiekty zbyt trywialne, bo ich
> implementacja wymaga pewnie utworzenia strumieni przy
> pomocy systemowych API lub zdobycia do nich uchwytów,
> oraz pewnie parę innych rzeczy, które mogłyby się
> nie udać.
Tak samo, jak w przypadku zmiennych globalnych stdin czy stdout w C.
Jeśli kanały standardowego IO nie mogą zostać otwarte, to wiadomo to
jeszcze przed uruchomieniem programu i system operacyjny może wtedy
podjąć odpowiednie akcje. Właściwy program zaczyna się wtedy, gdy
wszystkie deskryptory/klamki/itp. są już gotowe.
--
Maciej Sobczak : http://www.msobczak.com/
Programming : http://www.msobczak.com/prog/
>> Jak to jest robione w przypadku obiektów globalnych,
>> takich jak std::cin czy std::cout?
>
> Tak samo, jak w przypadku zmiennych globalnych stdin czy
> stdout w C. Jeśli kanały standardowego IO nie mogą zostać
> otwarte, to wiadomo to jeszcze przed uruchomieniem programu
Hmm... Czyli chyba zły przykład wybrałem :/
Ale konkretnie to chodzi mi o to, co się dzieje w przypadku,
gdy nie uda się utworzyć jakiegoś obiektu jeszcze przed
funkcją main(), bo np. brakło pamięci i nie da się jej już
przydzielić dla obiektu? Albo nie udało się otworzyć
jakiegoś pliku, który w funkcji main() miał być już
gotowy do zapisywania? Albo przed funkcją main() był
tworzony obiekt ładujący dynamicznie jakąś DLLkę, by jej
funkcje były w sposób "przezroczysty" dostępne już w main()?
Jest jakiś dobry mechanizm by powiadomić użytkownika, że
uruchomienie programu się nie powiodło i z jakiego powodu?
[może po prostu wypisać coś na stderr, skoro te strumienie
zawsze są dostępne, po czym zakończyć program?]
Co się stanie, gdy konstruktor takiego obiektu rzuci wyjątek
przed funkcją main()? Zostanie on gdzieś złapany przez
kod implementujący C++ ? czy może zostanie olany? :P
Bo chyba nie ma sposobu, żeby programista złapał wyjątek,
jeśli jest on poza funkcją main()? [chyba że o czymś nie wiem ;J]
Te pytania wciąż pozostają dla mnie bez odpowiedzi.
--
SasQ
> Ale konkretnie to chodzi mi o to, co się dzieje w przypadku,
> gdy nie uda się utworzyć jakiegoś obiektu jeszcze przed
> funkcją main()
No to, panie, bida.
> Jest jakiś dobry mechanizm by powiadomić użytkownika, że
> uruchomienie programu się nie powiodło
Tak, system operacyjny zwykle wywala w terminalu jakiś komunikat, że
poleciał niezłapany wyjątek.
Nie wiem, czy o taki "mechanizm" Ci chodzi. :-)
Na pociechę można podać fakt, że nawet w Adzie jest podobnie -
elaboracji części deklaratywnej głównego podprogramu nie da się objąć
obsługą wyjątków, co po ludzku oznacza, że coś tam się dzieje przed
pierwszym try. Z tym samym efektem, co w C++.
> Jest jakiś dobry mechanizm by powiadomić użytkownika, że
> uruchomienie programu się nie powiodło i z jakiego powodu?
OK, proste rozwiązanie dla C++:
#include <iostream>
#include <cstdlib>
// jakas klasa,
class C
{
public:
// ktorej konstruktor moze rzucic wyjatek:
C() { throw 0; }
void foo() { /* ... */ }
};
// zamiast obiektu globalnego klasy C robimy
// wrapperek,
struct CWrapper
{
CWrapper()
// ktory zlapie wyjatek z inicjalizacji obiektu klasy C,
try : c() {}
catch (...)
{
// i ladnie go obsluzy
std::cout << "Shit happened before main!\n";
std::exit(EXIT_FAILURE);
}
C c;
} g_CWrapper;
// a i tak mozna sie do niego odwolywac tak,
// jakby byl zadeklarowany globalnie:
C &c = g_CWrapper.c;
int main()
{
// tutaj widzimy c jak zwykly obiekt globalny:
c.foo();
}
Jeśli ten trick nie był jeszcze publikowany, to uprzejmie prosi się o
używanie nazwy "globalne try Sobczaka". ;-)
A jak już był opublikowany, to najwyraźniej ktoś mi ukradł pomysł zanim
na niego wpadłem (ktoś może oglądał film pt. "Maverick"?).
> Na pociechę można podać fakt, że nawet w Adzie jest podobnie -
> elaboracji części deklaratywnej głównego podprogramu nie da się objąć
> obsługą wyjątków, co po ludzku oznacza, że coś tam się dzieje przed
> pierwszym try. Z tym samym efektem, co w C++.
A w Kogucie można :-P
>> Ekran monitora istnieje od samego początku działania programu
>
> Ale nie graficzny. Wyobraź sobie program, który w zależności od
> sposobu uruchomienia korzysta z grafiki lub nie. Jeśli nie korzysta,
> to może być uruchomiony nawet tam, gdzie grafiki nie ma (np. sesja
> ssh bez tunelowania X-ów).
W moim przypadku akurat ma istnieć od początku ;)
Chodzi mi o uzyskanie pewnej abstrakcji ekranu graficznego w czymś
na kształt "silnika" do gier / biblioteki graficznej. Konieczność
"tworzenia" ekranu, czy jakiejś innej inicjalizacji ekranu zazwyczaj
wywołuje zdziwienie na twarzy początkujących użytkowników różnych
bibliotek graficznych, a przynajmniej u mnie i kilku innych osób
to zaobserwowałem ;J i spotkałem się ze stwierdzeniami: "Czemu tu
trzeba tworzyć ekran, jak on już istnieje?"]. Dlatego chciałem
ukryć przed użytkownikiem biblioteki całe to tworzenie ekranu i
w ten sposób dodatkowo uniezależnić go od implementacji tego ekranu.
Biblioteka SDL jest bliska temu rozwiązaniu, bo tam również jest to
w podobny sposób ukryte i użytkownik nie musi tworzyć ekranu, tylko
za pomocą funkcji SDL_SetVideoMode() pozyskuje strukturę opisującą
ekran już istniejący. Niestety tam tworzenie ekranu zrealizowano w
funkcji SDL_Init(), którą trzeba wywołać by korzystać z ekranu.
Ktoś może tego zapomnieć :P Dobrze zaprojektowany system nie powinien
polegać na ostrzeżeniach w dokumentacji [w stylu "zanim użyjesz ekranu,
musisz wywołać SDL_Init()"], lecz na dobrej spójnej konstrukcji i
i dobrych abstrakcjach ;J.
Chciałbym osiągnąć taki efekt, że użytkownik nie musi pamiętać o
inicjalizacji biblioteki, bo wykona się ona sama, już w samej
implementacji tej biblioteki, a ekran [czymkolwiek on będzie] będzie
już istniał od początku, gotowy do użycia i wołania na nim metod ;J
Nie musiałby też się przejmować, czym ten ekran jest: czy jest to
obszar roboczy wewnątrz okienka, okno na pełnym ekranie, bufor ramki
czy bitmapka zapisywana na dysku :P Po prostu weźmie ten gotowy ekran
i zacznie po nim rysować ;J
Moje założenie było właśnie takie, by używanie ekranu przypominało
używanie strumieni standardowego wejścia/wyjścia, które też są w
programie dostępne od początku jego istnienia i nie trzeba sobie
zawracać głowy ich inicjalizacją czy tworzeniem, bo tworzy je i
przygotowuje do pracy sama implementacja biblioteki.
Stąd moje pytania o inicjalizację obiektów statycznych, rzucanie
wyjątków gdy coś pójdzie źle podczas takiej inicjalizacji, oraz
możliwość reagowania użytkownika biblioteki na trudności z jej
inicjalizacją.
--
SasQ