Warsztat.GDCompo!ProjektyMediaArtykułyQ&AForumOferty pracyPobieranie

Opisz napotkaną sytuację, a redakcja niezwłocznie znajdzie rozwiązanie!

wyślij anuluj

Biblioteka FastDelegate

Wstęp

Artykuł opisuje bibliotekę FastDelegate, której autorem jest Don Clugston [1]. Służy ona do realizacji brakującego w języku C++ mechanizmu delegatów, czyli wskaźników na metody. Biblioteka dostępna jest na licencji Public Domain, a oryginalnie umieszczona i opisana została na portalu CodeProject. Ten artykuł stanowi jakby skróconą i przetłumaczoną na język polski dokumentację dla użytkowników biblioteki.

Wprowadzenie

W języku C++ istnieją wskaźniki do funkcji. Są przydatne do realizacji tzw. wywołań zwrotnych (ang. callback). Jeśli używasz Windows API, znasz na pewno jeden przykład - procedurę obsługi okna. Typowym przykładem zastosowania wywołań zwrotnych są też kontrolki GUI, np.:

Button *g_Button1;

void OnButtonClick()
{
  Beep(1000, 500);
}

void CreateGUI()
{
  g_Button1 = new Button();
  g_Button1.OnClick = &OnButtonClick;
}

Button to klasa reprezentująca przycisk, należąca do jakiejś zewnętrznej biblioteki. Potrafi odrysować wygląd przycisku oraz zareagować na jego kliknięcie, ale nie wie nic o tym, do czego służy konkretny przycisk. Tej samej klasy używamy do utworzenia przycisku "OK", "Anuluj", "Usuń" itd. Robimy to przypisując do pola OnClick wskaźnik na naszą własną funkcję, którą następnie przycisk wywoła w reakcji na kliknięcie.

Najprawdopodobniej jednak nie tylko przycisk jest klasą, ale używający go Twój kod również jest obiektowy. W takim wypadku właściciel przycisku, czyli zarazem ten, kto pragnie odbierać wywołania zwrotne od niego, również jest obiektem pewnej klasy. Powstaje więc potrzeba użycia wskaźnika do metody, nie do funkcji globalnej. Tu pojawia się wielki problem - w języku C++ najzwyczajniej brakuje takiego mechanizmu!

Istnieją wprawdzie w C++ wskaźniki na metody, ale potrafią one pokazywać na konkretną metodę o zgodnym nagłówku konkretnej klasy, bez związku z konkretnym obiektem. Nie są zbyt często przydatne w praktyce. Dużo częściej potrzebne są wskaźniki do pokazywania na KONKRETN¡ METODę O ZGODNYM NAG£ÓWKU W KONKRETNYM OBIEKCIE DOWOLNEJ KLASY. Coś takiego istnieje w wielu językach programowania, np. w Delphi, C#.

Mechanizm taki nazywany bywa różnie: wywołanie zwrotne (ang. callback), wskaźnik na metodę (ang. pointer to member), delegat (ang. delegate), zdarzenie (ang. event), sygnał i slot (ang. signal and slot). Jego brak w C++ próbują zrekompensować liczne biblioteki - m.in. sigslot [2], Boost.Signals [3] oraz opisywana tu FastDelegate [1]. Swój własny mechanizm delegatów posiadają też często biblioteki do GUI, np. wxWidgets, Qt, MFC.

Biblioteka FastDelegate

Biblioteka FastDelegate ma postać pojedynczego pliku nagłówkowego C++. Jest mała i bardzo prosta w użyciu. Jej autorem jest Don Clugston. Dostępna jest na licencji Public Domain, co oznacza, że można jej używać za darmo w dowolnych projektach, także komercyjnych. Biblioteka jest oryginalnie opisana i udostępniona do pobrania w [1], jednak z tym źródłem wiąże się kilka problemów. Po pierwsze, żeby ściągnąć plik, trzeba się zarejestrować w portalu CodeProject. Po drugie, artykuł jest bardzo długi, jest w języku angielskim, a opis używania biblioteki miesza się w nim ze szczegółami implementacyjnymi. Dlatego postanowiłem napisać ten tekst - sporządzić jakby alternatywną dokumentację biblioteki FastDelegate.

Biblioteka jest przenośna (m.in. na Windows, Linux) i działa na wielu kompilatorach, począwszy od Borlanda i Visual C++ 6, poprzez Visual C++ 2003, 2005, g++, aż po platformy 64-bitowe i Managed C++. Działa bardzo szybko - została napisana z myślą o wydajności. Oferuje wyłącznie jeden prosty mechanizm - wskaźniki na metody. W przeciwieństwie do wielu innych implementacji, nie wspiera takich "sygnałów", do których podłączyć można wiele "slotów" i wszystkie one zostaną wywołane na raz.

Podstawowe informacje:

  • Biblioteka obsługuje metody mające do 8 parametrów.
  • Zgodność sygnatury (liczby i typów parametrów, typu zwracanego) jest sprawdzana na etapie kompilacji.
  • Wszystkie składniki biblioteki zgromadzone są w przestrzeni nazw fastdelegate.
  • Delegaty są automatycznie zerowane w konstruktorze.
  • Można porównywać delegaty operatorami ==, !=.
  • Delegat może pokazywać zarówno na metodę w konkretnym obiekcie klasy, jak i na funkcję globalną czy metodę statyczną klasy.
  • Delegaty działają też poprawnie z metodami wirtualnymi, a nawet z dziedziczeniem wirtualnym, metodami abstrakcyjnymi itd.
  • Delegaty wywołuje się normalnie - za pomocą operatora ().

Przykładowa funkcja i metoda klasy, które będziemy przypisywali do delegatów w kodzie poniżej:

int FunkcjaGlobalna(const char *s)
{
  return 0;
}

class Klasa
{
public:
  int Metoda(const char *s)
  {
    return 0;
  }
  
  static void MetodaStatyczna(const char *s)
  {
    return 0;
  }
};

Klasa ObiektKlasy;

Oto definicja wskaźnika na funkcję lub metodę o sygnaturze: void NAZWA(int, int); Numer przy FastDelegate oznacza liczbę parametrów. Konstruktor domyślny automatycznie wyzeruje wskaźnik.

fastdelegate::FastDelegate2<int, int> Delegat1;

Oto definicja wskaźnika na funkcję lub metodę o sygnaturze: int NAZWA(const char*); Konstruktor od razu zainicjalizuje go podanym wskaźnikiem.

fastdelegate::FastDelegate1<const char*, int> Delegat2(&FunkcjaGlobalna);

Oto wyzerowanie wskaźnika:

Delegat2.clear();

Poniższy kod przypisuje wskaźnik do funkcji globalnej lub metody statycznej.

// Sposób 1
Delegat2.bind(&FunkcjaGlobalna);
Delegat2.bind(&Klasa::MetodaStatyczna);
// Sposób 2
Delegat2 = &FunkcjaGlobalna;
Delegat2 = &Klasa::MetodaStatyczna;

Zainicjalizowanie wskaźnika metodą klasy danego obiektu:

fastdelegate::FastDelegate1<const char*, int> Delegat3(
  &ObiektKlasy, &Klasa::Metoda);

Przypisanie wskaźnika do metody:

// Sposób 1
Delegat3.bind(&ObjektKlasy, &Klasa::Metoda);
// Sposób 2
Delegat3 = fastdelegate::MakeDelegate(
  &ObjektKlasy, &Klasa::Metoda);

Testowanie czy wskaźnik jest niezerowy i jeśli nie, wywołanie delegata:

int R;
// Sposób 1
if (Delegat3)
  R = Delegat3("abc");
// Sposób 2
if (!Delegat3.empty())
  R = Delegat3("abc");

Przykład

Poniżej znajduje się dłuższy, bardziej kompletny przykład wykorzystania delegatów do jakiejś zmyślonej biblioteki GUI. Kiedy obiektowi klasy Button ktoś jej wywołuje metodę MouseDown, w reakcji ona wywoła callback OnClick.

#include "FastDelegate.h"

class Button
{
public:
  // Typedef, dla wygody. Tak wygląda typ callbacka.
  // Ostatni, opcjonalny parametr szablonu to typ zwracany (domyślnie void).
  typedef fastdelegate::FastDelegate2<int, int, bool> ON_CLICK_DELEGATE;
  
  // Pole typu wskaźnik na metodę (callback)
  ON_CLICK_DELEGATE OnClick;

  void MouseDown(int x, int y)
  {
    // Sprawdzanie czy wskaźnik nie pusty
    if (OnClick)
      // Wywołanie
      bool Foo = OnClick(x, y);
  }
};

// Formularz - jest właścicielem przycisku, reaguje na jego callback
class Form1
{
public:
  // Przycisk
  Button m_Btn;

  Form1()
  {
    // Bindowanie callbacka - opcja 1
    m_Btn.OnClick.bind(this, &Form1::Button1_Click);
    
    // Bindowanie callbacka - opcja 2
    //m_Btn.OnClick = fastdelegate::MakeDelegate(
    //  this, &Form1::Button1_Click);
  }
  
  // Ta metoda będzie reagowała na callback
  bool Button1_Click(int x, int y)
  {
    Beep(1000, 100);
    return true;
  }
};

(...)

Form1 Obj;
Obj.m_Btn.MouseDown(1, 2);

Załącznik

Pobierz bibliotekę FastDelegate: FastDelegate.h (97 KB)

Bibliografia

  1. Don Clugston, Member Function Pointers and the Fastest Possible C++ Delegates, CodeProject, http://www.codeproject.com/KB/cpp/FastDelegate.aspx.
  2. sigslot - C++ Signal/Slot Library, http://sigslot.sourceforge.net/.
  3. Boost.Signals, http://www.boost.org/doc/libs/1_35_0/doc/html/signals.html.
Adam Sawicki
http://asawicki.info
19.05.2008

Tekst dodał:
Adam Sawicki
19.05.2008 16:49

Ostatnia edycja:
Adam Sawicki
05.05.2012 15:17

Kategorie:

Aby edytować tekst, musisz się zalogować.

# Edytuj Porównaj Czas Autor Rozmiar
#2 edytuj (poprz.) 05.05.2012 15:17 Adam Sawicki 9.59 KB (-37)
#1 edytuj (bież.) 19.05.2008 16:49 Adam Sawicki 9.63 KB
Zwykły
Do sprawdzenia
Do akceptacji
  • ~yarpen 19 maja 2008 21:34
    Fajniejsza jest libka Ryazanova. Szybsza i mniejsza.
  • Bleck (@Bleck) 21 maja 2008 01:12
    @yarpen: dzięki za info, dodam tylko, że "fajniejsza" to pojęcie względne

    @autor: dzięki za artykuł, z pewnością niejednej osobie się przyda
  • ~yarpen 21 maja 2008 15:12
    Fajniejsza byc moze. Mniejsza i szybsza juz nie. A mniejsza i szybsza dla mnie == fajniejsza.
  • Pockey (@Pockey) 14 czerwca 2008 01:02
    napisales ze to definicja delegata na funkcje f(char*) - cos chyba nie tak (ten dodatkowy int): fastdelegate::FastDelegate1<const char*, int> Delegat2(&FunkcjaGlobalna);
  • Pockey (@Pockey) 14 czerwca 2008 01:20
    ok juz wiem sory za zamieszanie ;)
  • ~Inco 09 stycznia 2009 13:49
    Bardzo fajna bibliotek i, dobry artykuł
  • Michał Korman (@dynax) 16 maja 2009 17:07
    Coś mi to nie działa. O wiele lepsze jest boost::function :)
  • Marcin "Furry" Olszewski (@Furry) 23 stycznia 2010 15:24
    imho świetna jest ta biblioteka szczególnie dla kogoś kto potrzebuje tylko ten problem tak szybko rozwiązać:)
  • Napisz komentarz:
    Aby dodać swój komentarz, musisz się zalogować.
Licencja Creative Commons

Warsztat używa plików cookies. | Copyright © 2006-2017 Warsztat · Kontakt · Regulamin i polityka prywatności
build #ff080b4740 (Tue Mar 25 11:39:28 CET 2014)