Funkcje zagnieżdżone i C++

2008-12-30 20:19

Niekiedy przydatna okazuje się możliwość definiowania funkcji widocznych wyłącznie w obrębie innej funkcji – czyli procedur zagnieżdżonych. Używa się tego najczęściej do krótkich, pomocniczych funkcji; oto najprostszy przykład w języku, który to umożliwia (Delphi):
[delphi]// oblicza odległość między dwoma punktami
function Distance(p1, p2 : TPoint) : Double;
function Square(X : Double) : Double;
begin
Result := X * X;
end;
begin
Result := Sqrt(Square(p1.X – p2.X) + Square(p1.Y – p2.Y));
end;[/delphi]
Takim językiem nie jest jednak C++, więc pewnie istnieje ku temu jakiś bardzo ważny powód… Bo czyż nie jest tak z każdą przydatną funkcjonalnością, która została w nim pominięta? ;D
Ale żarty na bok. W C++ można – przy odrobinie pomysłowości – osiągnąć dość podobny efekt:

  1. double Distance(POINT p1, POINT p2)
  2. {
  3.     static class
  4.     {
  5.         public:
  6.             double operator() (double x) const { return x * x; }
  7.     } Square;
  8.  
  9.     return sqrt(Square(p1.X - p2.X) + Square(p1.Y - p2.Y));
  10. }

Nie tworzymy tutaj funkcji, tylko odpowiednio nazwany obiekt anonimowej klasy, któremu przeciążamy operator nawiasów (wywołania funkcji). Efekt na oko jest taki sam: mamy pewną nazwę (Square), którą możemy użyć jak funkcję, lecz nie jest ona widoczna poza macierzystą procedurą (czyli Distance). Nasz obiekt deklarujemy też jako statyczny, dzięki koszt jego tworzenia i niszczenia ponosimy tylko raz, a ponadto nie zajmuje on miejsca na stosie (gdyż w innym przypadku z pewnością zająłby je; standard C++ mówi, że nawet obiekty klas bez zadeklarowanych pól nie mają rozmiaru 0).

Ta sztuczka jest jednak daleka od rzeczywistych funkcji zagnieżdżonych. W “prawdziwym” wydaniu mają one bowiem jedną ważną właściwość: posiadają dostęp do zmiennych lokalnych funkcji, w których są zawarte. W naszym przykładzie “funkcja” Square powinna więc móc odwołać się do (ewentualnych) zmiennych lokalnych z funkcji Distance. A ponieważ Square jest tutaj tak naprawdę obiektem jakiejś klasy, która nic nie wie o Distance, nie jest to rzecz jasna możliwe.
Czy możliwe jest więc, aby pełnowymiarowe funkcje zagnieżdżone pojawiły się np. w przyszłych wersjach języka C++?… Tutaj odpowiedź jest prawie na pewno negatywna. Jest tak dlatego, że obecność takich funkcji ingeruje bardzo głęboko w sposób, w jaki kompilator postępuje ze wszystkimi funkcjami. Pośrednimi konsekwencjami dodania funkcji zagnieżdżonych byłyby: niemożność korzystania ze standardowej konwencji wywoływania cdecl oraz zmiana wewnętrznej struktury wszystkich wskaźników na funkcje (które nie mogłyby być już pojedynczymi adresami).

Tags: ,
Author: Xion, posted under Programming »


19 comments for post “Funkcje zagnieżdżone i C++”.
  1. Gynvael:
    December 30th, 2008 o 21:05

    Bardzo ciekawy post ;> Nice! ;>

  2. agent_J:
    December 30th, 2008 o 21:13

    standard C++ jest jak impotent – nigdy nie może ;)

  3. Malcom:
    December 30th, 2008 o 21:44

    A nie powinien kompilator rozwinac kod tego funktora w miejscu wywolania, przeciez to w pelni inline’owa klasa?

  4. Charibo:
    December 30th, 2008 o 21:54

    A tak zupełnie szczerze mówiąc, to musiałem spojrzeć jak działa ten snippet w C++. A gdybym napotkał go gdzieś w czyimś kodzie, to bym się dobrze zastanowił po co takie kombinacje. :)

    Z drugiej strony, faktycznie, funkcje zagnieżdżone mogą się raz po raz przydać. :)

  5. krajek:
    December 30th, 2008 o 22:19

    Jak się nie ma co się lubi to się lubi co się ma :-).

    #define Square(x)((x)*(x))
    return sqrt(Square(p1.X – p2.X) + Square(p1.Y – p2.Y));
    #undef Square

  6. vashpan:
    December 31st, 2008 o 0:15

    Hmm. Jezyk D umozliwia zwyczajne funkcje zagniezdzone w sensie w jaki opisales. Tylko nie jest pewien czy jakos specjalnie “ingeruje” to w sposob w jaki kompilator traktuje wszystkie funkcje… W D istnieje przeciez mozliwosc uzywania standardowej konwencji wywolania cdecl ( tylko nie wiem na pewno czy tez do funkcji zagniezdzonych ) czy stosowania zwyczajnych, znanych z C wskaznikow na funkcje ( i stosowanie ich zwyczajnie w kodzie napisanym w C )

    Mysle ( aczkolwiek nie wiem na pewno ;) ) ze tam jest to inaczej rozwiazane niz myslisz, metoda/funkcja przekazuje do zagniezdzonej funkcji swoje lokalne zmienne jako niejawne parametry, podobnie jak wskaznik this, tylko ze parametry te sa tworzone “niejawnie” w momencie kompilacji. Wydaje mi sie ze cos takiego by zadzialalo… Oczywiscie niemozliwe jest wtedy uzywcie zagniezdzonej metody z zewnatrz lub uzycie konwencji cdecl ( ale tylko do niej )

    Ze specyfikacji jezyka:
    http://www.digitalmars.com/d/1.0/function.html – sekcja “Nested Functions” dosc sporo sie rozpisuje o tym.

    A tak w ogole taka opcja naprawde moglaby sie przydac. Czasami mi tego brak w C++,C# czy Javie…

  7. student_informatyki:
    December 31st, 2008 o 1:34

    Czy mógłby mi ktoś wyjaśnić, do czego to można w praktyce zastosować ? Co dzięki temu zyskuję ? Kiedy warto to używać ?

    Pozdrawiam

  8. Dabroz:
    December 31st, 2008 o 2:44

    A mi akurat prostota C++ w tej kwestii odpowiada. Bo jak potraktować z punktu widzenia OOP taką funkcję?

  9. agent_J:
    December 31st, 2008 o 7:45

    @student_informatyki: może się przydać np. gdy do jakiejś metody synchronicznej podajemy callback i nie chcemy się bawić w dodatkowe alokacje struktur przekazujących zmienne.

  10. vashpan:
    December 31st, 2008 o 10:11

    @Dabroz – takie funkcje przewaznie sa male i maja pelnic raczej pomocnicze funkcje. Ile razy mnie irytowalo kiedy musialem stworzyc prywatna niewielka metoda ktora byla wykorzystywana tylko przez jedna wieksza metode w klasie… Takie zagniezdzone funkcje moga nawet nieco poprawic “ladnosc” kodu klasy :D

  11. Riddlemaster:
    December 31st, 2008 o 10:14

    Do mnie funkcje zagnieżdżone jakoś nigdy nie przemawiały, więc ich brak w C++ wcale mi nie przeszkadza.

  12. Paweł Dziepak:
    December 31st, 2008 o 12:18

    AFAIK w C++0x zostanie wprowadzony operator lambda, więc możemy mówić o pewnego rodzaju zagnieżdżonych funkcjach w C++.

    @Malcom: może nie musi, każda funkcja zdefiniowana w definicji klasy jest inline, ale standard pozwala kompilatorom zignorowanie tego, najczęściej zależy to od ustawień optymalizacji.

  13. agent_J:
    December 31st, 2008 o 16:55

    Dopóki nie ma produkcyjnych kompilatorów C++0x, to gadanie, że coś będzie jest “full of shit” ;)

  14. Paweł Dziepak:
    December 31st, 2008 o 23:02

    Ludzie od GCC już się za to wzięli.
    Co prawda na ich liście rzeczy już zaimplementowanych jest zdecydowanie za dużo “No” żeby mówić o zgodności, ale obsługa operatora lambda jest w trakcie wprowadzania, więc pewnie w paru kolejnych wersjach się pojawi.

  15. Adawo:
    January 2nd, 2009 o 11:01

    Ciekawe rozwiązanie, nie powiem ;)

    W przypadku gcc wiadomo mi o takim rozszerzeniu w przypadku C, aczkolwiek C++ nie bardzo, a rzeczywiście przydało by się…

  16. Force:
    January 2nd, 2009 o 20:02

    Można w Delphi użyć cdecl za nagłówkiem każdej lub obu metod i się skompiluje, a wywołanie metod będzie jak w C++. A się przydaje gdy mamy metody które są używane tylko w jednym miejscu, to ładniej jest gdy z innych miejsc nie ma do nich dostępu, no i nie trzeba parametrów przekazywać bo mamy od razu dostęp do nich więc w sumie jest szybciej. W metodzie z klasy też można. Kompilator w sumie jedyne co robi to zmiana w generowanym kodzie adresów przesuwa o ileś tam bajótw. Widać to gdy najpierw w metodzie zewnętrznej zrobimy zmienną i domnozymy do resulta wewnętrznej, a potem w wewnętrznej zdefiniujemy to “a”. W sumie ciekawe czy da się wskazać które a chcemy użyć

  17. Xion:
    January 3rd, 2009 o 23:47

    “Kompilator w sumie jedyne co robi to zmiana w generowanym kodzie adresów przesuwa o ileś tam bajótw”
    Przesuwa pewnie o 4 bajty, czyli wielkość wskaźnika na leżące na stosie dane procedury zewnętrznej. W ten sposób funkcja wewnętrzna ma dostęp do zmiennych lokalnych funkcji zewnętrznej. Rzecz w tym, że adres ten (tzw. control link) trzeba położyć na stosie przed wywołaniem f. wewnętrznej – i to przy każdym wywołaniu. Stąd wynika więc też to, że każdy wskaźnik na tę funkcje musi zawierać ów control link, oprócz samego adresu funkcji.
    Być może są inne sposoby implementacji funkcji zagnieżdżonych, ale ja słyszałem tylko o tej :) (przynajmniej w językach kompilowanych do kodu maszynowego).

    “W sumie ciekawe czy da się wskazać które a chcemy użyć”
    W C++ jest operator zasięgu ::, nie pamiętam jak jest w Delphi :)

  18. Force:
    January 4th, 2009 o 13:27

    W Delphi jest operator zasięgu (.), ale wydaje mi się, że nie można nim wskazać, że chodzi nam o “a” z programu lub z której funkcji, można tylko który moduł oraz z rekordu/klasy.
    A przesuwa nie o 4 bajty, tylko o tyle aby wskazać dobrą zmienną :) czyli o wielkość porzedniej metody na stosie-adres zmiennej względem tej metody (ale namieszałem :D)

  19. moriturius:
    January 15th, 2009 o 6:37

    Prawdę mówiąc to naukę programowania zaczynałem od C++ więc funkcje zagnieżdżone, o których dowiedziałem się później, są dla mnie niesamowicie egzotycznym wynalazkiem.

    Jest to przydatne w ogóle?

Comments are disabled.
 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.