Standard C++

Wskaźniki do funkcji członkowskich

Czy typ „pointer-to-member-function” różni się od typu „pointer-to-function”?

Tak.

Rozważmy następującą funkcję:

int f(char a, float b);

Typ tej funkcji jest różny w zależności od tego, czy jest to zwykła funkcja, czy niestaticfunkcja członkowska jakiejś klasy:

Uwaga: jeśli jest to staticfunkcja członkowska class Fred, jej typ jest taki sam, jak gdyby była to zwykła funkcja:”int (*)(char,float)„.

Jak przekazać wskaźnik do funkcji członkowskiej do obsługi sygnału, wywołania zwrotnego zdarzenia X, wywołania systemowego, które uruchamia wątek/zadanie itp?

Nie rób tego.

Ponieważ funkcja członkowska jest bezsensowna bez obiektu, na którym można ją wywołać, nie możesz tego zrobić bezpośrednio (jeśli The X WindowSystem zostałby przepisany w C++, prawdopodobnie przekazywałby referencje do obiektów, a nie tylko wskaźniki do funkcji; naturalnie obiekty ucieleśniałyby wymagane funkcje i prawdopodobnie wiele więcej).

Jako poprawka do istniejącego oprogramowania, użyj funkcji najwyższego poziomu (nieczłonkowskiej) jako wrappera, który przyjmuje obiekt uzyskany za pomocą innej techniki. W zależności od procedury, którą wywołujesz, ta „inna technika” może być trywialna lub może wymagać trochę pracy z twojej strony. Na przykład, wywołanie systemowe, które uruchamia wątek, może wymagać przekazania wskaźnika funkcji wraz z void*, więc możesz przekazać wskaźnik obiektu w void*. Wiele systemów operacyjnych czasu rzeczywistego robi coś podobnego dla funkcji, która rozpoczyna nowe zadanie. W najgorszym przypadku możesz przechowywać wskaźnik obiektu w zmiennej globalnej; może to być wymagane w przypadku obsługi sygnałów systemu Unix (ale globale są generalnie niepożądane). W każdym razie, funkcja najwyższego poziomu wywołałaby pożądaną funkcję członkowską na obiekcie.

Oto przykład najgorszego przypadku (przy użyciu globali). Załóżmy, że chcesz wywołać Fred::memberFn() na przerwaniu:

Uwaga: static funkcje członkowskie nie wymagają rzeczywistego obiektu do wywołania, sopointers-to-static-funkcje członkowskie są zwykle kompatybilne typowo z regularnymi pointers-to-functions. Jednakże, chociaż prawdopodobnie działa to na większości kompilatorów, musiałaby to być extern "C" funkcja nieczłonkowska, aby była poprawna, ponieważ „C linkage” nie obejmuje tylko rzeczy takich jak mangling nazw, ale także konwencje wywoływania, które mogą być różne między C i C ++.

Dlaczego wciąż dostaję błędy kompilacji (niedopasowanie typu), gdy próbuję użyć funkcji członkowskiej jako procedury obsługi przerwania?

Jest to specjalny przypadek poprzednich dwóch pytań, dlatego najpierw przeczytaj poprzednie dwie odpowiedzi.

Nie-staticfunkcje członkowskie mają ukryty parametr, który odpowiada wskaźnikowi this. The this pointer pointsto the instance data for the object. Sprzęt/firmware przerwania w systemie nie jest w stanie dostarczyć argumentu wskaźnika this. Musisz użyć „normalnych” funkcji (nie będących członkami klasy) lub funkcji członkowskich static jako procedur obsługi przerwania.

Jednym z możliwych rozwiązań jest użycie funkcji static jako procedury obsługi przerwania i sprawienie, by ta funkcja szukała gdzieś pary instancja/członek, która powinna być wywołana przy przerwaniu. Efekt jest więc taki, że funkcja członka jest wywoływana na przerwaniu, ale z powodów technicznych musisz najpierw wywołać funkcję pośrednią.

Dlaczego mam problem z pobraniem adresu funkcji C++?

Krótka odpowiedź: jeśli próbujesz przechowywać go w (lub przekazać jako) wskaźnik do funkcji, to jest to problem – jest to następstwo poprzedniego FAQ.

Długa odpowiedź: W C ++ funkcje członkowskie mają niejawny parametr, który wskazuje na obiekt (the this pointerinside the member function). Normalne funkcje C mogą być uważane za mające inną konwencję wywoływania niż funkcje członkowskie, więc typy ich wskaźników (wskaźnik do funkcji członkowskiej vs wskaźnik do funkcji) są różne i niekompatybilne. C++ wprowadza nowy typ wskaźnika, zwany pointer-to-member, który może być wywołany tylko przez dostarczenie obiektu.

UWAGA: nie próbuj „rzucać” pointer-to-member-function na pointer-to-function; wynik jest niezdefiniowany i prawdopodobnie katastrofalny. Np. nie wymaga się, aby wskaźnik do funkcji-członka zawierał adres maszynowy odpowiedniej funkcji. Jak powiedziano w ostatnim przykładzie, jeśli masz wskaźnik do zwykłej funkcji C, użyj albo funkcji atop-level (nieczłonkowskiej), albo funkcji członkowskiej static (klasowej).

Jak mogę uniknąć błędów składni podczas tworzenia wskaźników do członków?

Użyj typedef.

Tak, racja, wiem: jesteś inny. Jesteś mądry. Potrafisz robić te rzeczy bez typedef. Sigh. Otrzymałem wiele e-maili od ludzi, którzy, tak jak ty, nie chcieli skorzystać z prostych rad zawartych w tym FAQ. Marnowali godziny i godziny swojego czasu, kiedy 10 sekund typedef uprościłoby im życie. Ponadto, spójrz prawdzie w oczy, nie piszesz kodu, który tylko ty możesz przeczytać; masz nadzieję, że piszesz swój kod, który inni również będą mogli przeczytać – kiedy są zmęczeni – kiedy mają swoje własne terminy i własne wyzwania. Dlaczego więc celowo utrudniać życie sobie i innym? Bądź mądry: używaj typedef.

Oto przykładowa klasa:

class Fred {public: int f(char x, float y); int g(char x, float y); int h(char x, float y); int i(char x, float y); // ...};

Typedef jest trywialny:

typedef int (Fred::*FredMemFn)(char x, float y); // Please do this!

To jest to! FredMemFn jest nazwą typu, a wskaźnik tego typu wskazuje na dowolnego członka Fred, który przyjmuje(char,float), takiego jak Fred’s f, g, h i i.

To jest wtedy trywialne, aby zadeklarować wskaźnik funkcji członka:

int main(){ FredMemFn p = &Fred::f; // ...}

I to jest również trywialne, aby zadeklarować funkcje, które otrzymują wskaźniki funkcji członka:

void userCode(FredMemFn p){ /*...*/ }

I to jest również trywialne, aby zadeklarować funkcje, które zwracają wskaźniki funkcji członka:

FredMemFn userCode(){ /*...*/ }

Więc proszę, użyj typedef. Albo to, albo nie wysyłaj mi e-maili o problemach, jakie masz ze swoimi wskaźnikami do funkcji członkowskich!

Jak mogę uniknąć błędów składni podczas wywoływania funkcji członkowskiej za pomocą wskaźnika do funkcji członkowskiej?

Jeśli masz dostęp do kompilatora i biblioteki standardowej, które implementują odpowiednie części nadchodzącego standardu C++17, użyj std::invoke. W przeciwnym razie użyj makra #define.

Proszę.

Pretty please.

Dostaję zdecydowanie zbyt wiele e-maili od zdezorientowanych ludzi, którzy odmówili skorzystania z tej rady. To jest takie proste. Wiem, że nie potrzebujeszstd::invoke ani makra, a ekspert, z którym rozmawiałeś, może to zrobić bez żadnego z nich, ale proszę, nie pozwól, aby twoje ego stanęło na drodze tego, co jest ważne: pieniędzy. Inni programiści będą musieli czytać / utrzymywać twój kod. Tak, wiem: jesteś mądrzejszy niż wszyscy inni; w porządku. I jesteś niesamowity; w porządku. Ale nie dodawaj niepotrzebnej złożoności do swojego kodu.

Używanie std::invoke jest banalne. Uwaga: FredMemFn jest typedef dla pointer-to-membertype:

Jeśli nie możesz użyć std::invoke, zmniejsz koszty utrzymania, paradoksalnie używając makra #define w tym konkretnym przypadku.

(Normalnie nie lubię makr #define, ale powinieneś ich używać z tomami wskaźników, ponieważ poprawiają czytelność i łatwość zapisu tego rodzaju kodu.)

Makro jest trywialne:

#define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember))

Używanie makra jest również trywialne. Uwaga: FredMemFn jest typedef dla pointer-to-membertype:

Powodem, dla którego std::invoke lub to makro jest dobrym pomysłem, jest to, że wywołania funkcji członkowskich są często o wiele bardziej złożone niż podany właśnie prosty przykład. Różnica w czytelności i łatwości pisania jest znacząca. comp.lang.c++ musiał znosić setki postów od zdezorientowanych programistów, którzy nie potrafili dobrze zrozumieć składni. Prawie wszystkie te błędy zniknęłyby, gdyby użyli oni std::invoke lub powyższego makra.

Uwaga: makra #define są złe na 4 różne sposoby: evil#1,evil#2, evil#3, i evil#4. Ale czasami wciąż są użyteczne. Ale i tak powinieneś czuć niejasne poczucie wstydu po ich użyciu.

Jak utworzyć i użyć tablicy pointer-to-member-function?

Użyj zarówno makra typedef, jak i std::invoke lub opisanego wcześniej makra #define, a na 90% skończysz.

Krok 1: utwórz makro typedef:

Krok 2: utwórz makro #define, jeśli nie masz std::invoke:

#define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember))

Teraz twoja tablica wskaźników do funkcji członkowskich jest prosta:

FredMemFn a = { &Fred::f, &Fred::g, &Fred::h, &Fred::i };

And your usage of one of the member function pointers is also straightforward:

void userCode(Fred& fred, int memFnNum){ // Assume memFnNum is between 0 and 3 inclusive: std::invoke(a, fred, 'x', 3.14);}

or if you don’t have std::invoke,

void userCode(Fred& fred, int memFnNum){ // Assume memFnNum is between 0 and 3 inclusive: CALL_MEMBER_FN(fred, a) ('x', 3.14);}

Note: Makra #define są złe na 4 różne sposoby: evil#1,evil#2, evil#3, and evil#4. Ale czasami wciąż są użyteczne. Czuj się zawstydzony, czuj się winny, ale gdy zła konstrukcja, taka jak makro, ulepsza twoje oprogramowanie, użyj jej.

Jak zadeklarować wskaźnik do funkcji członka, która wskazuje na funkcję członka const?

Krótka odpowiedź: dodaj const na prawo od ), gdy używasz typedef do zadeklarowania typu członka-funkcji.

Na przykład, załóżmy, że chcesz wskaźnik do funkcji członka, który wskazuje na Fred::f, Fred::g lub Fred::h:

class Fred {public: int f(int i) const; int g(int i) const; int h(int j) const; // ...};

Wtedy, gdy używasz typedef do zadeklarowania typu członka-funkcji-pointera, powinno to wyglądać tak:

To jest to!

Wtedy możesz deklarować/przekazywać/zwracać wskaźniki funkcji członkowskich tak jak normalnie:

Jaka jest różnica między operatorami .* i ->*?

Nie będziesz musiał tego rozumieć, jeśli używasz std::invoke lub makra do wywoływania funkcji członkowskich. Ohyea, proszę użyj std::invoke lub makra w tym przypadku. A czy wspomniałem, że powinieneś użyć std::invoke lub makra w tym przypadku??!?

Na przykład:

ALE proszę rozważyć użycie std::invoke lub makra zamiast:

void sample(Fred x, Fred& y, Fred* z, FredMemFn func){ std::invoke(func, x, 42, 3.14); std::invoke(func, y, 42, 3.14); std::invoke(func, *z, 42, 3.14);}

lub

Jak omówiono wcześniej, wywołania w świecie rzeczywistym są często o wiele bardziej skomplikowane niż te proste tutaj, więc użycie std::invoke lub makra zazwyczaj poprawi zapisywalność i czytelność twojego kodu.

Czy mogę przekonwertować wskaźnik do funkcji członkowskiej na void*?

Nie!

Szczegóły techniczne: wskaźniki do funkcji członkowskich i wskaźniki do danych niekoniecznie są reprezentowane w ten sam sposób. Apointer do funkcji członka może być strukturą danych, a nie pojedynczym wskaźnikiem. Pomyśl o tym: jeśli wskazuje na funkcję wirtualną, może w rzeczywistości nie wskazywać na statycznie rozwiązywalną kupę kodu, więc może nawet nie być normalnym adresem – może to być inna struktura danych jakiegoś rodzaju.

Proszę nie wysyłać mi e-maili, jeśli powyższe wydaje się działać na twojej konkretnej wersji twojego konkretnego kompilatora na twoim konkretnym systemie operacyjnym. Nie obchodzi mnie to. To jest nielegalne, kropka.

Czy mogę przekonwertować wskaźnik do funkcji na void*?

Nie!

Szczegóły techniczne: void* pointery są wskaźnikami do danych, a wskaźniki funkcji wskazują na funkcje. Język nie wymaga, aby funkcje i dane znajdowały się w tej samej przestrzeni adresowej, więc, jako przykład, a nie ograniczenie, na architekturach, które mają je w różnych przestrzeniach adresowych, dwa różne typy wskaźników nie będą porównywalne.

Proszę nie wysyłać mi e-maili, jeśli powyższe wydaje się działać na twojej konkretnej wersji twojego konkretnego kompilatora na twoim konkretnym systemie operacyjnym. Nie obchodzi mnie to. To nielegalne, kropka.

Potrzebuję czegoś w rodzaju wskaźników funkcyjnych, ale z większą elastycznością i/lub bezpieczeństwem wątków; czy jest inny sposób?

Użyj functionoid.

Co to do cholery jest functionoid i dlaczego miałbym go używać?

Funkcjoidy to funkcje na sterydach. Funkcjonoidy są bardziej potężne niż funkcje, a ta dodatkowa moc rozwiązuje niektóre (nie wszystkie) z wyzwań, przed którymi zwykle stajesz, gdy używasz wskaźników funkcji.

Popracujmy nad przykładem pokazującym tradycyjne użycie wskaźników funkcji, a następnie przełożymy ten przykład na funkcjonoidy. Tradycyjny pomysł z wskaźnikami funkcji polega na tym, że masz kilka kompatybilnych funkcji:

int funct1( /*...params...*/ ) { /*...code...*/ }int funct2( /*...params...*/ ) { /*...code...*/ }int funct3( /*...params...*/ ) { /*...code...*/ }

A następnie uzyskujesz do nich dostęp za pomocą wskaźników funkcji:

typedef int(*FunctPtr)( /*...params...*/ );void myCode(FunctPtr f){ // ... f( /*...args-go-here...*/ ); // ...}

Czasami ludzie tworzą tablicę tych wskaźników funkcji:

FunctPtr array;array = funct1;array = funct1;array = funct3;array = funct2;// ...

W takim przypadku wywołują funkcję poprzez dostęp do tablicy:

array( /*...args-go-here...*/ );

Z functionoids, najpierw tworzysz klasę bazową z metodą pure-virtual:

Potem zamiast trzech funkcji, tworzysz trzy klasy pochodne:

Potem zamiast przekazywania funkcji-pointer, przekazujesz Funct*. Utworzę typedef o nazwie FunctPtr tylko tomake reszta kodu podobna do staromodnego podejścia:

typedef Funct* FunctPtr;void myCode(FunctPtr f){ // ... f->doit( /*...args-go-here...*/ ); // ...}

Możesz utworzyć tablicę z nich w prawie taki sam sposób:

To daje nam pierwszą wskazówkę na temat tego, gdzie functionoidy są zdecydowanie potężniejsze od function-pointers: fakt, że podejście functionoidowe ma argumenty, które możesz przekazać do ctorów (pokazane powyżej jako …ctor-args…), podczas gdy wersja function-pointers nie. Pomyśl o obiekcie typu functionoid jako o liofilizowanym wywołaniu funkcji (z naciskiem na słowo wywołanie). W przeciwieństwie do wskaźnika do funkcji, functionoid jest (konceptualnie) wskaźnikiem do częściowo wywołanej funkcji. Wyobraźmy sobie przez chwilę technologię, która pozwala nam przekazać do funkcji kilka, ale nie wszystkie argumenty, a następnie pozwala nam zamrozić to (częściowo zakończone) wywołanie. Udawaj, że ta technologia daje ci z powrotem jakiś magiczny wskaźnik do tego zamrożonego, częściowo zakończonego wywołania funkcji. Następnie później przekazujesz pozostałe argumenty używając tego wskaźnika, a system w magiczny sposób bierze twoje oryginalne argumenty (które zostały zamrożone), łączy je z dowolnymi zmiennymi lokalnymi, które funkcja obliczyła przed zamrożeniem, łączy to wszystko z nowo przekazanymi argumentami i kontynuuje wykonywanie funkcji tam, gdzie została przerwana, gdy została zamrożona. To może brzmieć jak science fiction, ale to jest właśnie to, na co pozwalają funktory. Dodatkowo pozwalają one na wielokrotne „dokończenie” tej zamrożonej funkcji z różnymi różnymi „pozostałymi parametrami”, tak często jak tylko chcesz. Plus pozwalają (nie wymagają) na zmianę stanu liofilizowanego, gdy zostanie wywołany, co oznacza, że funktory mogą zapamiętać informacje od jednego wywołania do następnego.

Postawmy nasze stopy z powrotem na ziemi i popracujemy nad kilkoma przykładami, aby wyjaśnić, co wszystkie te mumbo jumbo reallymeans.

Załóżmy, że oryginalne funkcje (w staromodnym stylu function-pointer) przyjęły nieco inne parametry.

int funct1(int x, float y){ /*...code...*/ }int funct2(int x, const std::string& y, int z){ /*...code...*/ }int funct3(int x, const std::vector<double>& y){ /*...code...*/ }

Gdy parametry są różne, staromodne podejście function-pointer jest trudne w użyciu, ponieważ wywołujący nie wie, które parametry przekazać (wywołujący ma tylko wskaźnik do funkcji, a nie nazwę funkcji lub, gdy parametry są różne, liczbę i typy jej parametrów) (nie pisz do mnie maila na ten temat; tak, możesz to zrobić, ale musisz stanąć na głowie i robić niechlujne rzeczy; ale nie pisz do mnie na ten temat – użyj zamiast tego functionoids).

W przypadku funktoroidów sytuacja jest, przynajmniej czasami, znacznie lepsza. Ponieważ functionoid może być uważany za liofilizowane wywołanie funkcji, po prostu weź niepopularne argumenty, takie jak te, które nazwałem y i/lub z, i zrób z nich argumenty do odpowiednich ctorów. Możesz również przekazać wspólne argumenty (w tym przypadku int o nazwie x) do ctor, ale nie musisz – masz możliwość przekazania go/ich do czystej wirtualnej metody doit() zamiast tego. I’ll assumeyou want to pass x into doit() and y and/or z into the ctors:

class Funct {public: virtual int doit(int x) = 0;};

Then instead of three functions, you create three derived classes:

Now you see that the ctor’s parameters get freeze-dried into the functionoid when you create the array ofunctionoids:

Więc kiedy użytkownik wywołuje doit() na jednym z tych functionoidów, dostarcza on „pozostałe” argumenty, a wywołanie konceptualnie łączy oryginalne argumenty przekazane do ctor z tymi przekazanymi do metody doit():

array->doit(12);

Jak już wspomniałem, jedną z zalet funkcyjnychoidów jest to, że możesz mieć kilka instancji, powiedzmy, Funct1 w swojej tablicy, a te instancje mogą mieć różne parametry zamrożone w nich. Na przykład, array iarray są typu Funct1, ale zachowanie array->doit(12) będzie inne niż zachowaniearray->doit(12), ponieważ zachowanie będzie zależało zarówno od 12, które zostało przekazane do doit(), jak i od argumentów przekazanych do ctors.

Inna korzyść z funktorów jest widoczna, jeśli zmienimy przykład z tablicy funktorów na localfunctionoid. Aby ustawić scenę, wróćmy do staromodnego podejścia funkcja-pointer i wyobraźmy sobie, że próbujesz przekazać funkcję porównania do procedury sort() lub binarySearch(). Procedura sort() lub binarySearch() nazywa się childRoutine(), a typ funkcji porównania-pointer nazywa się FunctPtr:

void childRoutine(FunctPtr f){ // ... f( /*...args...*/ ); // ...}

Wtedy różni rozmówcy przekazaliby różne wskaźniki funkcji w zależności od tego, co uważali za najlepsze:

void myCaller(){ // ... childRoutine(funct1); // ...}void yourCaller(){ // ... childRoutine(funct3); // ...}

Możemy łatwo przetłumaczyć ten przykład na taki z użyciem functionoids:

Dając ten przykład jako tło, możemy zobaczyć dwie korzyści z functionoids nad function-pointers. Korzyść „ctor args” opisana powyżej, plus fakt, że funktory mogą utrzymywać stan pomiędzy wywołaniami w sposób bezpieczny dla wątków.W przypadku zwykłych wskaźników funkcyjnych, ludzie zazwyczaj utrzymują stan pomiędzy wywołaniami poprzez statyczne dane. Jednak dane statyczne nie są wewnętrznie bezpieczne dla wątków – dane statyczne są współdzielone przez wszystkie wątki. Podejście functionoid zapewnia ci coś, co jest wewnętrznie bezpieczne dla wątków, ponieważ kod kończy się na danych lokalnych dla wątków. Implementacja jest trywialna: zmień staromodną statyczną bazę danych na dane instancji wewnątrz obiektu this functionoida, i puf, dane są nie tylko lokalne dla wątków, ale są nawet bezpieczne przy wywołaniach rekurencyjnych: każde wywołanie yourCaller() będzie miało swój własny odrębny obiekt Funct3 z własnymi odrębnymi danymi instancji.

Zauważ, że zyskaliśmy coś bez utraty czegokolwiek. Jeśli chcesz wątek-globalne dane, functionoids może dać youthat zbyt: wystarczy zmienić go z członka danych instancji wewnątrz obiektu functionoid this do statycznego członka danychwithin klasy functionoid, lub nawet do local-scope statyczne dane. Nie byłoby lepiej niż w przypadku function-pointers, ale nie byłoby też gorzej.

Podejście functionoid daje trzecią opcję, która nie jest dostępna w staromodnym podejściu: thefunctionoid pozwala dzwoniącym zdecydować, czy chcą danych thread-local czy thread-global. Byliby odpowiedzialni za uselocks w przypadkach, w których chcieliby danych globalnych wątku, ale przynajmniej mieliby wybór. To proste:

Funkcjonoidy nie rozwiązują każdego problemu napotkanego podczas tworzenia elastycznego oprogramowania, ale są zdecydowanie potężniejsze niż wskaźniki funkcji i warto je przynajmniej ocenić. W rzeczywistości można łatwo udowodnić, że funktoroidy nie tracą żadnej mocy w stosunku do funktoropunktorów, ponieważ można sobie wyobrazić, że staromodne podejście funktoropunktorów jest równoważne posiadaniu globalnego(!) obiektu funktoroidu. Ponieważ zawsze możesz stworzyć globalny obiekt funkcyjny, nie straciłeś żadnego gruntu. QED.

Czy można zrobić functionoidy szybciej niż normalne wywołania funkcji?

Tak.

Jeśli masz mały functionoid, a w prawdziwym świecie jest to raczej powszechne, koszt wywołania funkcji może być wysoki w porównaniu z kosztem pracy wykonanej przez functionoid. W poprzednim FAQ, functionoidy zostały zaimplementowane przy użyciu funkcji wirtualnych i zazwyczaj będą kosztować cię wywołanie funkcji. Alternatywne podejście wykorzystuje szablony.

Następujący przykład jest podobny w duchu do tego z poprzedniego FAQ. Zmieniłem nazwę doit() naoperator()(), aby poprawić czytelność kodu wywołującego i umożliwić komuś przekazanie zwykłego wskaźnika funkcji:

Różnica między tym podejściem a tym w poprzednim FAQ polega na tym, że fuctionoid zostaje „związany” z wywołującym w czasie kompilacji, a nie w czasie wykonywania. Pomyśl o tym jak o przekazywaniu parametru: jeśli znasz w czasie kompilacji rodzaj functionoid, który ostatecznie chcesz przekazać, to możesz użyć powyższej techniki i możesz, przynajmniej w typowych przypadkach, uzyskać korzyść prędkości z posiadania kompilatorainline-expand kodu functionoid w wywołującym. Oto przykład:

template <typename FunctObj>void myCode(FunctObj f){ // ... f( /*...args-go-here...*/ ); // ...}

Gdy kompilator skompiluje powyższe, może on inline-expand the call whichmight improve performance.

Oto jeden ze sposobów na wywołanie powyższego:

void blah(){ // ... Funct2 x("functionoids are powerful", 42); myCode(x); // ...}

Aside: jak zostało zasugerowane w pierwszym akapicie powyżej, możesz również przekazać nazwy normalnych funkcji (choć możesz ponieść koszt wywołania funkcji, gdy wywołujący ich użyje):

void myNormalFunction(int x);void blah(){ // ... myCode(myNormalFunction); // ...}

Jaka jest różnica między funktorem a funktorem?

Funktor jest obiektem, który ma jedną główną metodę. Jest to w zasadzie rozszerzenie OO funkcji podobnej do C, takiej jak asprintf(). Funktor jest specjalnym przypadkiem funktionoidu: jest to funktionoid, którego metodą jest „operator wywołania funkcji”, „operator()()”. Ponieważ przeciąża on operator wywołania funkcji, kod może wywołać jego główną metodę używając tej samej składni, co w przypadku wywołania funkcji. Np. jeśli „foo” jest funktorem, to aby wywołać metodę „operator()()” na obiekcie „foo” powiemy „foo()”. Korzyść z tego jest w szablonach, ponieważ wtedy szablon może mieć parametr szablonu, który będzie użyty jako funkcja, a ten parametr może być albo nazwą funkcji, albo funktora-obiektu. Istnieje korzyść wydajnościowa z bycia obiektem funktora, ponieważ metoda „operator()()” może być inlined (podczas gdy jeśli przekazujesz adres funkcji, musi ona być, koniecznie, nie-inlined).

Jest to bardzo użyteczne dla rzeczy takich jak funkcja „comparison” na posortowanych kontenerach. W C, funkcja porównania jest zawsze przekazywana przez wskaźnik (np, patrz podpis do „qsort()”), ale w C++ parametr może przyjść jako wskaźnik do funkcji LUB jako nazwa funktora-obiektu, a wynikiem tego jest to, że posortowane pojemniki w C++ mogą być, w niektórych przypadkach, o wiele szybsze (i nigdy wolniejsze) niż odpowiednik w C.

Ponieważ Java nie ma nic podobnego do szablonów, musi używać dynamicznego wiązania dla wszystkich tych rzeczy, a dynamiczne wiązanie z konieczności oznacza wywołanie funkcji. Normalnie nie jest to wielka sprawa, ale w C++ chcemy umożliwić ekstremalnie wysoką wydajność kodu. To znaczy, C++ ma filozofię „płać za to tylko wtedy, gdy tego używasz”, co oznacza, że język nigdy nie może arbitralnie nakładać żadnych narzutów ponad to, co maszyna fizyczna jest w stanie wykonać (oczywiście programista może, opcjonalnie, używać technik takich jak dynamiczne wiązanie, które, ogólnie rzecz biorąc, będą nakładać pewne narzuty w zamian za elastyczność lub inną „zdolność”, ale to projektant i programista decydują, czy chcą korzyści (i koszty) takich konstrukcji).

Leave a Reply