2010-10-14

emit Alert(StackOverflow);

Qt jest dość popularnym API do obsługi okien i nie tylko. Warto wspomnieć, że na jego bazie powstał interfejs graficzny KDE, mnóstwo większych i mniejszych aplikacji jak Skype, Gadu-Gadu, Google Earth, Kate oraz reszta aplikacji z pakietu KDE. API jest przemyślane, wieloplatformowe, kod przenosi się łatwo, ponadto w samym Qt wmontowano chyba wszystko, z czego typowy developer korzysta tworząc natywną aplikację. Jako, że każda platforma i każde API ma swoje niuanse, dzisiaj o przepełnianiu stosu za pomocą sygnałów i slotów - właśnie w Qt.


Do wywołań zwrotnych, powiadamiania itd. powstał mechanizm sygnałów i slotów. Użycie jest bardzo proste. W klasie "informującej" o zdarzeniu, w nagłówku, definiujemy sygnały, za pomocą których przekazywane będą powiadomienia. Podobnie jak zakres dostępu do składowych - tyle, że zamiast public:, protected:, czy private: używamy signals: - i tak otrzymujemy (przykładowo).
signals:
void connected();
void disconnected();
void connectionError();
void authorizationRequired();
Natomiast w klasie "informowanej" (to może być ta sama klasa, o łączeniu sygnałów ze slotami dalej) definiujemy sloty. Sposób jest podobny, tyle że konieczne jest też zawarcie specyfikatora dostępu, skąd mamy np.:
public slots:
void onConnected();
void onDisconnected();
protected slots:
void onAuthorized();
private slots:
void onLoopEnded();
Ważna uwaga: każdy slot traktowany jest jak funkcja wirtualna - nawet jeśli go nie używamy, musi zostać zaimplementowany, inaczej pojawi się błąd linkera.

Jak połączyć sygnały i sloty? Sposób jest prostszy, niż by się wydawało - używamy metody statycznej QObject::connect. W podstawowej składni takowe połączenie ma postać: QObject::connect(sender, SIGNAL(connected()), receiver, SLOT(onConnected())); Dużo nawiasów, makra - można to zignorować, grunt, że będzie działać. Dla oszczędzenia pisania istnieje niestatyczna metoda connect, która de facto wywołuje QObject::connect(sender, signal, this, slot);

Miało być o przepełnieniu stosu, a rozpisałem się o podstawach użycia sygnałów i slotów. Otóż, sprawa jest ściśle powiązana. Poprzez instrukcję emit connected(); wywoływane są wszystkie metody połączone z tym sygnałem. Co ważne - wywołanie jest w miejscu użycia słowa emit - czyli w efekcie nakładają się na stos. Wyobraźmy sobie proste połączenie - QTcpSocket::connected() połączone do onConnected() oraz connected() również połączone do onConnected(). W connected() wywołujemy (emitujemy) onConnected() - typowy błąd, nieskończona rekurencja, przepełnienie stosu.

Inna sytuacja, gdy używamy sygnałów i slotów jako wyjątków, przekazujących informację coraz wyżej. Wszystkie parametry przekazywane są przez kopiowanie(!), więc stos buduje się coraz wyższy w trakcie "powrotu" w górę. W efekcie przy kilku-kilkunastu warstwach osiągamy znane i nielubiane stack overflow. Może na PC wyda się to abstrakcją (x86 zakłada domyślnie 1MB stosu), ale na Symbianie, gdzie stos jest liczony w dziesiątkach kB, uważanie na każdy emit jest jak najbardziej właściwe.

Oczywiście nie chcę zniechęcać do używania tego mechanizmu Qt - moim zdaniem to najlepszy mechanizm delegatów, jaki powstał w dowolnym natywnym API (niektórymi cechami przebija nawet eventy/delegaty z .NET), jeśli będziecie pamiętać o stosie i nie przesadzać z "łańcuszkami emitów" czy sygnalizowaniem rekurencyjnym, wszystko powinno działać dobrze.

Brak komentarzy:

Prześlij komentarz