background image

 

 

1

Biblioteka STL - funktory

Andrzej Walczak

Pierwsza edycja-WAT 2006

background image

 

 

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
 

background image

 

 

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; 

background image

 

 

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ć. 

background image

 

 

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),...) 

background image

 

 

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); 

background image

 

 

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 

background image

 

 

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łej
Należy dodać, że typ const f_type & nie 
jest obsługiwany przez kompilator g++-3.3 ale 
przez g++-3.4 już tak 

background image

 

 

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). 

background image

 

 

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 

background image

 

 

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. 

background image

 

 

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. 

background image

 

 

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ą.

background image

 

 

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;

background image

 

 

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;

background image

 

 

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.

background image

 

 

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;    

background image

 

 

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.

background image

 

 

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;}

background image

 

 

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.

background image

 

 

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.

background image

 

 

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)();}

background image

 

 

23

Predefiniowane obiekty funkcyjne

Arithmetic operations 

plus

 

minus

 

multiplies

 (formerly called "times") 

divides

 

modulus

 

negate

 

background image

 

 

24

Predefiniowane obiekty funkcyjne

Comparisons 

equal_to

 

not_equal_to

 

less

 

greater

 

less_equal

 

greater_equal

 

background image

 

 

25

Predefiniowane obiekty funkcyjne

Logical operations 

logical_and

 

logical_or

 

logical_not

 

background image

 

 

26

Predefiniowane obiekty funkcyjne

Generalized identity operations 

identity

 

project1st

 

project2nd

 

select1st

 

select2nd

 

background image

 

 

27

Predefiniowane obiekty funkcyjne 

-arytmetyczne

Plus<T> is a 

function object

. 

Specifically, it is an 

Adaptable Binary Function

. 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. 

background image

 

 

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;

vector

<double> 

V1(N);vector<double> V2(N);vector<double> V3(N);

•  

iota

(V1.begin(), V1.end(), 1)//przypisuje do zakresu 

wartosc rosnąco

fill

(V2.begin(), V2.end(), 75); //przypisuje do zakresu 

wartosc

• assert(V2.size() >= V1.size() && V3.size() >= V1.size());

transform

(V1.begin(), V1.end(), V2.begin(), V3.begin(),  

        plus<double>());//plus jest obiektem funkcyjnym

background image

 

 

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.

background image

 

 

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. 

background image

 

 

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>());

background image

 

 

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.

background image

 

 

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}; 


Document Outline