Podczas implementacji funkcji zwrotnej w C ++, czy nadal powinienem używać wskaźnika funkcji w stylu C:
void (*callbackFunc)(int);
Czy powinienem użyć std :: function:
std::function< void(int) > callbackFunc;
Podczas implementacji funkcji zwrotnej w C ++, czy nadal powinienem używać wskaźnika funkcji w stylu C:
void (*callbackFunc)(int);
Czy powinienem użyć std :: function:
std::function< void(int) > callbackFunc;
Odpowiedzi:
Krótko mówiąc, używaj,std::function
chyba że masz powód, aby tego nie robić.
Wskaźniki funkcji mają tę wadę, że nie są w stanie uchwycić kontekstu. Nie będziesz mógł na przykład przekazać funkcji lambda jako funkcji zwrotnej, która przechwytuje niektóre zmienne kontekstowe (ale zadziała, jeśli nie przechwyci żadnych). Wywołanie zmiennej this
składowej obiektu ( tj. Niestatycznej) również nie jest zatem możliwe, ponieważ obiekt ( -pointer) musi zostać przechwycony. (1)
std::function
(od C ++ 11) służy przede wszystkim do przechowywania funkcji (przekazywanie jej nie wymaga przechowywania). Dlatego jeśli chcesz przechowywać wywołanie zwrotne na przykład w zmiennej składowej, prawdopodobnie jest to najlepszy wybór. Ale także jeśli go nie przechowujesz, jest to dobry „pierwszy wybór”, chociaż ma tę wadę, że wprowadza pewne (bardzo małe) narzuty podczas wywoływania (więc w bardzo krytycznej sytuacji może to być problem, ale w większości nie powinno). Jest bardzo „uniwersalny”: jeśli bardzo zależy Ci na spójnym i czytelnym kodzie, a także nie chcesz myśleć o każdym wyborze, którego dokonujesz (tj. Chcesz, aby był prosty), używaj go std::function
dla każdej funkcji, którą przekazujesz.
Pomyśl o trzeciej opcji: jeśli masz zamiar zaimplementować małą funkcję, która następnie zgłasza coś przez dostarczoną funkcję zwrotną, rozważ parametr szablonu , który może być dowolnym wywoływalnym obiektem , tj. Wskaźnikiem funkcji, funktorem, lambdą, a std::function
, ... Wadą jest to, że twoja (zewnętrzna) funkcja staje się szablonem i dlatego musi być zaimplementowana w nagłówku. Z drugiej strony masz tę zaletę, że wywołanie zwrotne może być wbudowane, ponieważ kod klienta Twojej (zewnętrznej) funkcji „widzi” wywołanie wywołania zwrotnego będzie zawierał dokładną informację o typie.
Przykład wersji z parametrem szablonu (napisz &
zamiast &&
przed C ++ 11):
template <typename CallbackFunction>
void myFunction(..., CallbackFunction && callback) {
...
callback(...);
...
}
Jak widać w poniższej tabeli, wszystkie mają swoje zalety i wady:
+-------------------+--------------+---------------+----------------+
| | function ptr | std::function | template param |
+===================+==============+===============+================+
| can capture | no(1) | yes | yes |
| context variables | | | |
+-------------------+--------------+---------------+----------------+
| no call overhead | yes | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be inlined | no | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be stored | yes | yes | no(2) |
| in class member | | | |
+-------------------+--------------+---------------+----------------+
| can be implemented| yes | yes | no |
| outside of header | | | |
+-------------------+--------------+---------------+----------------+
| supported without | yes | no(3) | yes |
| C++11 standard | | | |
+-------------------+--------------+---------------+----------------+
| nicely readable | no | yes | (yes) |
| (my opinion) | (ugly type) | | |
+-------------------+--------------+---------------+----------------+
(1) Istnieją sposoby obejścia tego ograniczenia, na przykład przekazanie dodatkowych danych jako dalszych parametrów do (zewnętrznej) funkcji: myFunction(..., callback, data)
wywoła callback(data)
. To jest "wywołanie zwrotne z argumentami" w stylu C, które jest możliwe w C ++ (a przy okazji bardzo często używane w WIN32 API), ale powinno się go unikać, ponieważ mamy lepsze opcje w C ++.
(2) Chyba że mówimy o szablonie klasy, tj. Klasa, w której przechowujesz funkcję, jest szablonem. Ale to oznaczałoby, że po stronie klienta typ funkcji decyduje o typie obiektu, który przechowuje wywołanie zwrotne, co prawie nigdy nie jest opcją w rzeczywistych przypadkach użycia.
(3) W przypadku wersji przed C ++ 11 użyj boost::function
std::function
z usuniętym typem, jeśli trzeba ją natychmiast przechowywać poza zwany kontekstem).
void (*callbackFunc)(int);
może być funkcją zwrotną w stylu C, ale jest okropnie bezużyteczna o kiepskim projekcie.
Wygląda dobrze zaprojektowany callback w stylu C void (*callbackFunc)(void*, int);
- ma on void*
pozwolić kodowi wykonującemu wywołanie zwrotne na utrzymanie stanu poza funkcją. Nie wykonanie tego zmusza dzwoniącego do globalnego przechowywania stanu, co jest niegrzeczne.
std::function< int(int) >
int(*)(void*, int)
w większości implementacji jest nieco droższe niż wywołanie. Jednak niektórym kompilatorom trudniej jest wbudować. Istnieją std::function
implementacje klonów, które rywalizują z narzutami wywołania wskaźnika funkcji (zobacz „najszybsi możliwe delegaci” itp.), Które mogą przedostać się do bibliotek.
Teraz klienci systemu wywołań zwrotnych często muszą konfigurować zasoby i pozbywać się ich, gdy wywołanie zwrotne jest tworzone i usuwane, a także mieć świadomość okresu istnienia wywołania zwrotnego. void(*callback)(void*, int)
nie zapewnia tego.
Czasami jest to możliwe poprzez strukturę kodu (wywołanie zwrotne ma ograniczony czas życia) lub przez inne mechanizmy (wyrejestrowanie wywołań zwrotnych i tym podobne).
std::function
zapewnia środki do ograniczonego zarządzania przez cały okres użytkowania (ostatnia kopia obiektu znika, gdy zostaje zapomniana).
Ogólnie użyłbym znaku, std::function
chyba że wydajność dotyczy manifestu. Gdyby tak było, najpierw szukałbym zmian strukturalnych (zamiast wywołania zwrotnego na piksel, co powiesz na wygenerowanie procesora linii skanowania w oparciu o przekazaną mi lambdę? Co powinno wystarczyć, aby zmniejszyć obciążenie wywołań funkcji do trywialnych poziomów. ). Następnie, jeśli problem będzie się powtarzał, zapisywałbym na delegate
podstawie najszybszych możliwych delegatów i sprawdzał, czy problem z wydajnością zniknie.
Przeważnie używałbym wskaźników funkcji tylko dla starszych interfejsów API lub do tworzenia interfejsów C do komunikacji między różnymi generowanymi kodami kompilatorów. Używałem ich również jako wewnętrznych szczegółów implementacji, kiedy implementuję tabele skoków, wymazywanie typów itp.: Kiedy tworzę i konsumuję je, i nie udostępniam ich zewnętrznie do użycia przez jakikolwiek kod klienta, a wskaźniki funkcji robią wszystko, czego potrzebuję .
Zauważ, że możesz napisać otoki, które zamieniają się std::function<int(int)>
w int(void*,int)
wywołanie zwrotne stylu, zakładając, że istnieje odpowiednia infrastruktura zarządzania okresem życia wywołania zwrotnego. Więc jako test dymny dla dowolnego systemu zarządzania czasem życia wywołań zwrotnych w stylu C, upewniłbym się, że opakowanie std::function
działa w miarę dobrze.
void*
wzięło? Dlaczego miałbyś chcieć utrzymywać stan poza funkcją? Funkcja powinna zawierać cały kod, którego potrzebuje, całą funkcjonalność, wystarczy przekazać jej żądane argumenty, zmodyfikować i coś zwrócić. Jeśli potrzebujesz jakiegoś stanu zewnętrznego, dlaczego funkcja functionPtr lub callback miałaby przenosić ten bagaż? Myślę, że oddzwonienie jest niepotrzebnie skomplikowane.
this
. Czy to dlatego, że trzeba wziąć pod uwagę przypadek wywołania funkcji składowej, więc potrzebujemy this
wskaźnika, aby wskazywał na adres obiektu? Jeśli się mylę, czy mógłbyś podać mi link, gdzie mogę znaleźć więcej informacji na ten temat, ponieważ nie mogę znaleźć wiele na ten temat. Z góry dziękuję.
void*
zezwolenie na transmisję stanu środowiska wykonawczego. Wskaźnik funkcji z argumentem void*
i void*
argumentem może emulować wywołanie funkcji składowej do obiektu. Przepraszam, nie znam zasobu przechodzącego przez „projektowanie mechanizmów wywołania zwrotnego C 101”.
this
. O to mi chodziło. W każdym razie dzięki.
Służy std::function
do przechowywania dowolnych wywoływalnych obiektów. Pozwala użytkownikowi podać dowolny kontekst potrzebny do wywołania zwrotnego; zwykły wskaźnik funkcji nie.
Jeśli z jakiegoś powodu musisz użyć zwykłych wskaźników do funkcji (być może dlatego, że potrzebujesz interfejsu API zgodnego z C), powinieneś dodać void * user_context
argument, aby był przynajmniej możliwy (aczkolwiek niewygodny), aby uzyskać dostęp do stanu, który nie jest bezpośrednio przekazywany do funkcjonować.
Jedynym powodem, dla którego należy unikać, std::function
jest obsługa starszych kompilatorów, które nie obsługują tego szablonu, który został wprowadzony w C ++ 11.
Jeśli obsługa języka pre-C ++ 11 nie jest wymagana, użycie std::function
daje wywołującym większy wybór w implementacji wywołania zwrotnego, co czyni go lepszą opcją w porównaniu do „zwykłych” wskaźników funkcji. Daje użytkownikom twojego API większy wybór, jednocześnie wyodrębniając specyfikę ich implementacji dla twojego kodu, który wykonuje wywołanie zwrotne.