1
Biblioteka STL - funktory
Andrzej Walczak
Pierwsza edycja-WAT 2006
2
Typy funkcyjne
• Zanim
zajmiemy
się
bardziej
skomplikowanymi obiektami jakimi są
funktory,
wyjaśnijmy
kilka
faktów
dotyczących funkcji i wskaźników do nich.
Funkcje nie są obiektami. Nie można ich
kopiować ani przypisywać do siebie:
• void f(double x) {};
void g(double) = f; niedozwolonevoid
h(double);h=f; niedozwolone
3
Typy funkcyjne
• Funkcje posiadają jednak typ. Typ funkcji
(nazywany typem funkcyjnym) jest określony
przez typ jej wartości zwracanej i typy jej
argumentów. Typy funkcyjne możemy używać np.
w poleceniu typedef. Wyrażenie:
• typedef void f_type(double) definiuje f_type jako
typ funkcji o jednym argumencie typu double i nie
zwracającej żadnej wartości. Taki typ ma jednak
ograniczone zastosowanie, możemy go używać do
deklarowania, ale nie definiowania innych funkcji:
• f_type g;
4
Typy funkcyjne
• Typ funkcyjny może też być użyty jako
parametr szablonu:
• template<typename F> Function {F
_fun;};Function<void (double)>
• Niewiele jednak będziemy mieli pożytku z pola
_fun, bo jak już widzieliśmy, nie będziemy w
stanie nic do niego przypisać ani go
zainicjalizować.
5
Typy funkcyjne
• Możemy również używać typów funkcyjnych w
deklaracjach argumentów funkcji. Wyrażenie:
• double sum(double (double),...) oznacza że
funkcja sum oczekuje jako pierwszego argumentu
funkcji zwracającej double o jednym argumencie
typu double. Ten zapis jest jednak mylący! W
rzeczywistości nie można przekazać funkcji jako
argumentu wywołania i dlatego w deklaracjach
argumentów typ funkcyjny jest automatycznie
zamieniany na typ wskaźnika do funkcji i
powyższa deklaracja jest równoważna deklaracji:
• double sum(double (*)(double),...)
6
Wskaźniki do funkcji
• Wskaźniki do funkcji są
normalnymi obiektami i mogą być
kopiowane i przypisywane:
• void f(double x) {};
void (*g)(double) = &f;void (*h)
(double);h=&f;(*h)(0.0);(*g)(1.0);
7
Wskaźniki do funkcji
• C++ posiada wygodną własność, która jednak
zwiększa konfuzję pomiędzy funkcjami i
wskaźnikami do nich. Otóż operatory * i & są
aplikowane automatycznie do funkcji i
powyższy kod można zapisać jako:
• void f(double x) {};
void (*g)(double) = f;void (*h)
(double);h=&f;h(0.0);g(1.0); Jest to dość
wygodne, ale powoduje, że część ludzi słabo
rozróżnia funkcje od wskaźników do nich
8
Referencje do funkcji
• Żeby już skończyć ten temat i zupełnie
zamieszać Państwu w głowach napiszę, że
można też definiować referencje do funkcji:
• void f(double x) {};typedef void f_type(double)
f_type &g = f; f_type &h = g;const f_type
&ch = g; równoważne z wyrażeniem f_type &ch
= g;h=g; niedozwolone h jest refencją do
stałejNależy dodać, że typ const f_type & nie
jest obsługiwany przez kompilator g++-3.3 ale
przez g++-3.4 już tak
9
Dedukcja typów funkcyjnych
• Ponieważ funktory i funkcje często przekazywane są jako
argumenty wywołania szablonów, których typ podlega
dedukcji, warto wiedzieć jak ten mechanizn rozpoznaje
typ przekazywanej funkcji. Rozważmy najpierw
następujacą definicję:
• template<typename F> test(F f) { F _fun(f);} Jeśli teraz
wywołamy
• void (*g)(double) = f ; void (&h)(double) = f;
test(f); F = void (*)(double)test(g); F = void (*)
(double)test(h); F = void (*)(double)to w każdym
przypadku typ F zostanie wydedukowany jako wskaźnik
do funkcji void (*)(double).
10
Dedukcja typów funkcyjnych
• Jeśli przypomnimy sobie, że
argumenty do funkcji można dla
oszczędności przekazywać jako
referencje do stałych, to możemy
napisać:
• template<typename F> test(const
F &f) { F _fun(f);} czeka nas
jednak niepospodzianka, kod
11
Dedukcja typów funkcyjnych
• void (*g)(double) = f ; void (&h)(double) = f;
test(f); F = void ()(double), nie skompiluje
siętest(g); F = void (*)(double)test(h); F =
void ()(double) nie skompiluje się zachowuje się już
inaczej. W przypadku przekazania funkcji lub referencji
do funkcji, wededukowanym typem będzie typ
funkcyjny. Ponieważ nie można inicjalizować zmiennych
typu funkcyjnego, wyrażenie F _fun(f) się nie
skompiluje. Nie będzie kłopotów jeśli przekażemy
wskaźnik do funkcji, ale tym razem musimy to zrobić
jawnie, nie nastąpi automatyczna konwersja nazwy
funkcji na wskaźnik do niej.
12
Dedukcja typów funkcyjnych
• Można by przedefiniować funkcję test następująco:
• template<typename F> test(const F &f) { F *_fun(f);}
Teraz wywołania
• test(f); F = void ()(double)test(h); F = void ()
(double) skompilują się, ale nie skompiluje się linijka
• test(g); F = void (*)(double) bo pole _fun stanie się
typu void (**)(double). Kompilator g+-3.3 nawet tego
kodu nie skompiluje, bo nie zezwala na referencje do
stałych typów funkcyjnych.
• Widać więc, że przy przekazywaniu funkcji najlepiej
używać wywołania przez wartość, która i tak jest
automatycznie konwertowana na przekazywanie
wskaźnika do funkcji.
13
Funktory jako obiekty o cechach
funkcji
• W C++ operator wywołania funkcji ( ) może
być traktowany jak każdy inny operator. W
szczególności może być przeciążany czyli
mogą mu być przypisywane różne zadania do
wykonania. Ograniczeniem jest to, że
może
być przeciążany tylko jako metoda klasy
.
• Każdy obiekt zawierający definicję
operatora wywołania funkcji nazywany
jest funktorem. Taki obiekt zachowuje się
jakby był funkcją.
14
Funktory jako obiekty o cechach
funkcji
• Przykład zwykłej funkcji, która sumuje liczby z
przedziału {n,m}:
double f(double x) {
return 2*x
;}
double sum(double (*f)(double),int n,int m){
double result=0;
for(int i=n;i<=m,i++) result = f(i);
return result; }
Teraz taką funkcję możemy wywołać jako:
cout<<sum(f,1,5)<<endl;
Albo jako dowolną inną funkcję:
cout<<sum(sin,1,5)<<endl;
15
Funktory jako obiekty o cechach
funkcji
•Taka sama funkcja wykorzystująca obiekt funkcyjny czyli
funktor:
class classf{public:
classf(){}//
definiuje funktor, czyli obiekt klasy classf
wywołany przez ()
};
double sum2(classf, int n, int m){ double result = 0;
for (int i=n; i<+m,i++)result+=f(i);
return result;}
W tej funkcji pierwszy argument nie jest wskaźnikiem na
funkcję tylko obiektem funkcyjnym czyli
funktorem.Utworzony jest konstruktorem domniemanym.
Wywołanie takiej funkcji może być następujące:
cout<<sum2(classf(),2,5)<<endl;
16
Funktory jako obiekty o cechach
funkcji
• Obiekt funkcyjny to zwykły obiekt klasy. Jego cechą
szczególną jest to, że przeciąża operator wywołania
funkcji. Można dla niego wywołać implementowaną
metodę klasy albo kilka metod operatorem () tak jak
dla zwykłej funkcji.
• Może zawierać swoje metody i zmienne czyli ma
swój stan przy wywołaniu i stan operacji, którą
wykonuje.
• Funkcja, którą reprezentuje funktor może mieć
jednocześnie różne stany, czego zwykła funkcja nie
potrafi. Czyli funktor przechowuje swoje stany
pośrednie.
17
Funktory jako obiekty o cechach
funkcji
• Weźmy ciąg Fibonacciego: 1,1,2,3,5,8,13,...
• Do obliczeń zapiszmy kod:
• class Fib{ public: Fib( ):a0_(1),a1_(1) { }; int
operator() (); private: int a0_,a1_;};
• int Fib::operator () {int temp=a0; a0=a1;
a1=temp+a0; return temp;}
• Teraz możemy zrobić wywołanie:
• Fib fib;///...cout<<”ELEMENTY
CIĄGU”<<fib()<<‘ ’<<fib()<<endl;
18
Funktory jako obiekty o cechach
funkcji
• fib() będzie rozpoznany jako wywołanie na
rzecz obiektu fib metody przeciążającej
operator(). Taki sam efekt daje fib.operator().
• Korzyścią jest to, że obiekt zachowuje stan
między wywołaniami co pozwala zapamiętać
kolejne elementy ciągu w obiekcie klasy Fib.
Przy zwykłej funkcji nie jest to możliwe,
Trzeba stosować posiłkowe zmienne statyczne
albo globalne, albo jawnie przekazywać
wartość do funkcji.
19
Funktory jako obiekty o cechach
funkcji
• Rozpatrzmy problem całkowania funkcji. Musimy
w takiej operacji przechowywać wartości
policzone w poszczególnych krokach całkowania.
typedef double(*F)*(double);
double integrate (F f, double low, double high){
const int numsteps=9;
double step=(high-low)/numsteps;
double area=0.0;
while(low<high){area+=f(low)*step; low+=step;}
return area;}
20
Funktory jako obiekty o cechach
funkcji
• Musimy do funkcji całkującej przekazać wskaźnik funkcji
całkowanej:
double aFunc(double x){...}//
double area=integrate (aFunc,0.0,2.7128)
NIE
PRZECHOWA
ON
JEDNAK WARTOŚCI W
POSZCZEGÓLNYCH
KROKACH
CAŁKOWANIA.
Nie
scałkuje także metod klas czyli funkcji składowych
będących metodami obiektów.
Należy zdefiniować funktor:
class Func{ public:
virtual ~Func(); virtual double operator()(double)=0;};
double integrate (Func &f, double low, double high);
Teraz w ramach dziedziczenia każda funkcja polimorficzna
z Func może być całkowana. Także wtedy, kiedy jest metodą
w klasie dziedziczącej.
21
Funktory jako obiekty o cechach
funkcji
Obiekty funkcyjne posiadają więc składnię
wywołania funkcji. Ale są obiektami czyli mają
konstruktory, destruktory, metody, zmienne, stan
zapisany w składowych obiektu. Definiowanie
własnych funktorów wykonujemy poprzez
przeciążenie operatora (). Możemy zastosować
strukturę w miejsce klasy:
strukt Sin( double operator())(double x) {return
sin(x);}}
Możemy z tego funktora korzystać jak z funkcji sinus.
Ma on te przewagę nad funkcją sinus, że
przechowuje swój stan.
22
Funktory jako obiekty o cechach
funkcji-wskaźniki do metod
• Przy wskaźniku do zwykłej funkcji nie musimy stosować
* ani &. Przypadek funkcji jako metody obiektu zmusza
do jawnego pobrania jej adresu, zniesienia referencji
(aliasu) na ten adres. Metoda ma także niejawny
argument, który jest wskaźnikiem this na obiekt, który ją
wywołuje. Bez niego nie możemy się odwołać do metody:
class X { void f() {std::cout<<``f1``<<`` ``};};
main(){typedef void(X::*f_ptr)();
f_ptr pf1=&X::f;// pobieranie adresu f pod wskaźnik f_ptr
X x;
X *px=new X;
(x.*pf1)(); //wykonanie funkcji f na rzecz obiektu x
(px->*pf1)();}
23
Predefiniowane obiekty funkcyjne
Arithmetic operations
•
•
•
(formerly called "times")
•
•
•
24
Predefiniowane obiekty funkcyjne
Comparisons
•
•
•
•
•
•
26
Predefiniowane obiekty funkcyjne
Generalized identity operations
•
•
•
•
•
27
Predefiniowane obiekty funkcyjne
-arytmetyczne
Plus<T> is a
Specifically, it is an
. If f is
an object of class plus<T> and x
and y are objects of class T, then
f(x,y) returns x+y.
28
Predefiniowane obiekty funkcyjne
-arytmetyczne
• Example
• Each element in V3 will be the sum of the
corresponding elements in V1 and V2
• const int N = 1000;
<double>
V1(N);vector<double> V2(N);vector<double> V3(N);
•
(V1.begin(), V1.end(), 1)//przypisuje do zakresu
wartosc rosnąco
•
(V2.begin(), V2.end(), 75); //przypisuje do zakresu
wartosc
• assert(V2.size() >= V1.size() && V3.size() >= V1.size());
•
(V1.begin(), V1.end(), V2.begin(), V3.begin(),
plus<double>());//plus jest obiektem funkcyjnym
29
Predefiniowane obiekty funkcyjne
-arytmetyczne
Member
Where defined
Description
first_argument_type
Adaptable Binary
Function
The type of the first argument:
T
second_argument_type
Adaptable Binary
Function
The type of the second argument:
T
result_type
Adaptable Binary
Function
The type of the result:
T
T operator()(const T& x, const T& y)
Adaptable Binary
Function
Function call operator. The return
value is
x + y
.
plus()
Default
Constructible
The default constructor.
30
Predefiniowane obiekty funkcyjne
-arytmetyczne
• Multiplies<T> [1] is a function
object. Specifically, it is an
Adaptable Binary Function. If f is
an object of class multiplies<T>
and x and y are objects of class T,
then f(x,y) returns x*y.
31
Predefiniowane obiekty funkcyjne
-arytmetyczne
• Example
• Each element in V3 will be the product of the
corresponding elements in V1 and V2
• const int N = 1000;vector<double>
V1(N);vector<double> V2(N);vector<double>
V3(N); iota(V1.begin(), V1.end(),
1);fill(V2.begin(), V2.end(),
75); transform(V1.begin(), V1.end(),
V2.begin(), V3.begin(),
multiplies<double>());
32
Funktory c.d.
• Funktory są więc także rozumiane jako uogólnione
wskaźniki do funkcji. Można je wywoływać
bezpośrednio, tak, jak funkcje. Można także
stosować je w algorytmach STL do wykonywania
zadania jako odpowiednik funkcji na liście
argumentów innej funkcji.
• Często takie uogólnienie wskaźnika służy do
rozdzielenia miejsca i czasu wykonania definicji
funkcji od jej zastosowania. Wygląda to tak, ze
klient definiuje funkcje i wskaźniki do nich
przekazuje do aplikacji. Klient nie wie kiedy i jak
zostaną wykonane, a aplikacja nie wie jak działają.
Nazywa się to techniką punktów zwrotnych.
33
Adaptery funktorów
• Obiekty mogą jednak posiadać więcej informacji
niż tylko swój stan, mogą zawierać również
informacje o typie. Przede wszystkim funktory
same posiadają typ, konsekwencje tego faktu
omówię poźniej, teraz zajmę się typami
stowarzyszonymi. Nasuwająca się od razu
możliwość, to stowarzyszenie z funktorem typu
wartości zwracanej oraz typów jego argumentów.
Można stowarzyszyć również informację o liczbie
argumentów. W przypadku szablonu moglibyśmy
dodać na przykład
• typedef double result_type;typedef double
argument_type;enum {n_arguments = 1};