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:
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).
Bardzo ciekawy post ;> Nice! ;>
standard C++ jest jak impotent – nigdy nie może ;)
A nie powinien kompilator rozwinac kod tego funktora w miejscu wywolania, przeciez to w pelni inline’owa klasa?
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ć. :)
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
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…
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
A mi akurat prostota C++ w tej kwestii odpowiada. Bo jak potraktować z punktu widzenia OOP taką funkcję?
@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.
@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
Do mnie funkcje zagnieżdżone jakoś nigdy nie przemawiały, więc ich brak w C++ wcale mi nie przeszkadza.
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.
Dopóki nie ma produkcyjnych kompilatorów C++0x, to gadanie, że coś będzie jest “full of shit” ;)
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.
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ę…
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ć
“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 :)
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)
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?