arytmetyczne, relacyjne, logiczne

Transkrypt

arytmetyczne, relacyjne, logiczne
obiekty funkcyjne (funktory)
Obiekty klas, w których przeciążono operator funkcyjny (wywołania funkcji)
• Najczęstsze wykorzystanie
jako predykaty (orzeczniki logiczne) używane w ogólnych algorytmach
• Mogą zawierać dodatkowe dane potrzebne do wykonania operacji
Obiekty funkcyjne biblioteki standardowej – wzorce klas
Zdefiniowane w pliku nagłówkowym <functional>
arytmetyczne, relacyjne, logiczne
plus<T>
minus<T>
multiplies<T>
divides<T>
modulus<T>
negate<T>
equal_to<T>
not_equal_to<T>
greater<T>
greater_equal<T>
less<T>
less_equal<T>
logical_and<T>
logical_or<T>
logical_not<T>
obiekty funkcyjne – przykłady
multiplies<double> m;
equal_to<char> e;
logical_or<int> l;
cout<< m(2.5, 3.1) <<” ”<< e(’a’, ’a’) <<” ”<< l(3, 1); // 7.75 1 1
int t[] = { 5, -7, 3, 1, -1, 2 };
list<int> l(t, t+6); // w C++11 list<int> l {5, -7, 3, 1, -1, 2};
l.sort( less<int>() );
// jest też algorytm sort( RandomIt, RandomIt, Pred );
// ale działa na iteratorach „dostępu swobodnego”, a takie
// nie obsługują listy – dlatego lista ma własną metodę sort (i inne)
ostream_iterator<int> out(cout, " ");
copy(l.begin(), l.end(), out); // -7 -1 1 2 3 5
vector<int> v(t, t+6);
sort( v.begin(), v.end(), greater<int>() );
copy( v.begin(), v.end(), out ); // 5 3 2 1 -1 -7
obiekty funkcyjne – przykłady
struct intsort {
bool operator() (const int& l, const int& r) { return l < r; }
};
// można użyć własny funktor
l.sort( intsort() );
// dzięki szablonowi można pokazać dowolny typ sekwencyjny
template<typename T>
struct prezentuj {
prezentuj(ostream& o, string s = "") : strumien(o), separator(s) { }
void operator () (const T& p) { strumien << p << separator; }
private:
ostream& strumien;
string separator;
};
template <typename T>
void view(T con) {
for_each(con.begin(), con.end(), prezentuj<typename T::value_type>(cout, " -=- "));
}
// w programie...
view(v); view(l);
funktory – typy argumentów i zwracanych wartości
Aby umożliwić adaptorom i innym komponentom operacje na obiektach funkcyjnych,
przyjmujących jeden lub dwa argumenty, wymagane jest aby funktory te dostarczały
przypisanie (typedef) odpowiednich typów nazwom argument_type i result_type
(dla funktorów jednoargumentowych) oraz first_argument_type,
second_argument_type i result_type (dla funktorów dwuargumentowych).
Standardową procedurą ułatwiającą te definicje,
jest dziedziczenie z następujących klas bazowych:
template <class Arg, class Result>
struct unary_function {
typedef Arg argument_type;
typedef Result result_type;
};
template <class Arg1, class Arg2, class Result>
struct binary_function {
typedef Arg1 first_argument_type;
typedef Arg2 second_argument_type;
typedef Result result_type;
};
adaptory obiektów funkcyjnych – wiązadła
wzorce klas pozwalające na zmianę funkcjonalności obiektów funkcyjnych
nie używa się ich bezpośrednio lecz za pomocą funkcji zwracających obiekty tych klas
wiązadło (poniższe to „historyczne” wersje dla C++98)
przekształca dwuargumentowy funktor w jednoargumentowy, związując
jeden z argumentów z konkretną wartością
bind1st – wiąże pierwszy argument – szablon funkcji tworzący funktor typu binder1st
bind2nd – wiąże drugi argument – szablon funkcji tworzący funktor typu binder2nd
– funktory te przechowują dwa argumenty,
przekazane w wywołaniu bind1st() / bind2nd()
– pierwszy argument musi być dwuargumentową funkcją (lub funktorem)
– operator() obiektu binder1st / binder2nd wywołuje przekazaną funkcję dwuargumentową,
podając jej otrzymany argument wywołania i zapamiętaną wartość zadaną, odpowiednio
jako pierwszy / drugi argument
// zawartość listy: -7 -1 1 2 3 5
l.remove_if(bind1st( less<int>(), 1 )); // 1 < prawy argument kolejne wyrazy z listy
// z oryginalnej zawartości zostaną: -7 -1 1
l.remove_if(bind2nd( less<int>(), 1 )); // lewy argument kolejne wyrazy z listy < 1
// z oryginalnej zawartości zostaną: 1 2 3 5
A gdyby użyć np. equal_to<int> ? Wtedy wszystko jedno czy bind1st czy bind2nd –
bo argumenty operatora == są symetryczne.
std::bind ( C++11 ), bind1st, bind2nd – „przestarzałe”
bind1st, bind2nd – są ograniczone, wiążą tylko pierwszy lub drugi argument
– nie mogą wiązać funkcji z argumentem typu „referencja do”
– nierzadko wymagają adaptacji obiektów funkcyjnych
(za pomoca ptr_fun, mem_fun, mem_fun_ref)
obiekt_funkcyjny std::bind( wielkość_wywoływalna, 1arg, 2arg, …, Narg );
Zastępcze obiekty pozwalają na mapowanie argumentów bind
z argumentami wielkości wywoływalnej. Notacja: _n oznacza n-ty
argument przekazany do obiektu funkcyjnego zwracanego przez bind.
l.remove_if( bind ( less<int>(), 1, _1 )); // to samo co bind1st wcześniej
l.remove_if( bind ( less<int>(), _1, 1 )); // to samo co bind2nd wcześniej
Uwaga: zastępczy obiekt ma nazwę _1 (dla kolejnych parametrów _2 itd.)
Te obiekty są formalnie umieszczone w przestrzeni nazw
std::placeholders. Jeśli widzimy błąd kompilacji, trzeba dodać jedno z:
using namespace std::placeholders; // cała przestrzeń nazw
using std::placeholders::_1; // dyrektywa użycia konkretnego obiektu
std::bind - przykłady
#include <iostream>
#include <functional>
using namespace std;
using namespace std::placeholders;
int suma1( int n1, int n2, int& n3, int& n4 ) {
n3 = 0; n4 = 0;
return n1+n2+n3+n4;
}
int main() {
cout << bind( suma1, 1, 2, 3, 4 )() << endl; // 3, argumenty przez wartość
int var1 = 1, var2 = 2, var3 = 3, var4 = 4;
cout << bind( suma1, var1, var2, var3, var4 )() << endl; // 3, jak wyżej
cout << var1 << ", " << var2 << ", " << var3 << ", " << var4 << endl; // 1, 2, 3, 4
var1 = 1, var2 = 2, var3 = 3, var4 = 4;
cout << bind( suma1, var1, var2, ref(var3), var4 )() << endl; // 3
cout << var1 << ", " << var2 << ", " << var3 << ", " << var4 << endl; // 1, 2, 0, 4
var1 = 1, var2 = 2, var3 = 3, var4 = 4;
cout << bind( suma1, var1, _2, _1, var4 )(var2,var3) << endl; // 4 placeholders przez referencję
cout << var1 << ", " << var2 << ", " << var3 << ", " << var4 << endl; // 1, 0, 3, 4
}
cout << bind( suma1, _1, _2, _5, var4 )(77, var2, var3, var4, var1) << endl;
std::bind – dalsze przykłady
Wysłanie do strumienia:
void wypisz( ostream& strumien, int n ) {
strumien << n << endl;
}
bind( wypisz, ref(cout), 777)();
list<int> ll { 1,2,3,4,5 };
for_each( ll.begin(), ll.end(), bind(wypisz, ref(cout), _1) );
Zabawy z initializer_list:
int suma(initializer_list<int> il) {
int sum {0};
for ( auto n : il ) { cout<<n<<" "; sum+=n; }
return sum;
}
// w programie:
// to nie działa: bind( suma, { 1, 2, 3, 4, 5 } )();
int sumka = bind( suma, initializer_list<int>{ 1, 2, 3, 4, 5 } )(); // ok
cout << bind( suma, _1 )( initializer_list<int>{ 1, 2, 3, 4, 5 } ); // ok
negator
adaptory obiektów funkcyjnych – negatory (C++98)
to funkcja, która zmienia wartość logiczną funktora na wartość przeciwną
not1 – odwraca wartość logiczną funktora jednoargumentowego
not2 – odwraca wartość logiczną funktora dwuargumentowego
// negacja unary_function
template <class Predicate>
unary_negate<Predicate> not1(const Predicate& pred);
// negacja binary_function
template <class Predicate>
binary_negate<Predicate> not2(const Predicate& pred);
// zawartość listy: -7 -1 1 2 3 5
l.remove_if( not1( bind1st( equal_to<int>(), 3 ) ) ); // bind nie działa… patrz dalej
// z oryginalnej zawartości zostanie: 3
l.sort( not2( less<int>() ) );
// to samo jak użycie do sortowania greater<int>
// czyli oryginalna zawartość: 5 3 2 1 -1 -7
adaptory wskaźników do funkcji – ptr_fun (C++98)
Zamiast samodzielnie dziedziczyć z klas unary_function czy binary_function,
można użyć specjalnego adaptora ptr_fun, przyjmującego wskaźnik do funkcji
i konwertującego ją do postaci obiektu funkcyjnego. Funkcje te muszą być jedno- lub
dwuargumentowe (nie mogą być bezargumentowe).
template <class Arg, class Result>
pointer_to_unary_function<Arg, Result>
ptr_fun(Result (*f)(Arg)); // jednoargumentowa
template <class Arg1, class Arg2, class Result>
pointer_to_binary_function<Arg1,Arg2,Result>
ptr_fun(Result (*f)(Arg1, Arg2)); // dwuargumentowa
Typy zwracane przez funkcję ptr_fun rzeczywiście dziedziczą z odpowiednich klas:
template <class Arg, class Result>
class pointer_to_unary_function :
public unary_function<Arg, Result>;
template <class Arg1, class Arg2, class Result>
class pointer_to_binary_function :
public binary_function<Arg1,Arg2,Result>;
ptr_fun – przykład (i alternatywy w C++11)
#include <string>
#include <iostream>
#include <algorithm>
#include <functional>
bool isvowel(char c) {
return std::string("aeoiuAEIOU").find(c) != std::string::npos;
}
int main()
{
std::string s = "Hello, world!";
std::copy_if(s.begin(), s.end(), std::ostreambuf_iterator<char>(std::cout),
std::not1(std::ptr_fun(isvowel)));
// C++11 alternatywy:
//
std::not1(std::cref(isvowel)));
//
std::not1(std::function<bool(char)>(isvowel)));
// UWAGA: bind nie działa z not1 gdyż bind nie definiuje argument_type
// jak tego oczekuje not1
}
adaptory do metod składowych – mem_fun (C++98), mem_fn (C++11)
Gdy chcemy zaadaptować metodę z klasy, przekazując ją przez wskaźnik do składowej klasy,
używamy adaptor mem_fun, który generuje obiekt funkcyjny. Jego metoda, która ma być
docelowo użyta, jest wywołana za pomocą wskaźnika do tego obiektu. Są wersje dla metod
z przydomkiem const i dla metod modyfikujących (jak poniżej):
template<class S, class T>
mem_fun_t<S,T> mem_fun(S (T::*f)()); // bezargumentowa
template<class S, class T, class A>
mem_fun1_t<S,T,A> mem_fun(S (T::*f)(A)); // jednoargumentowa
Tym razem zwracany typ to:
template <class S, class T>
class mem_fun_t : public unary_function<T*, S> { public:
explicit mem_fun_t(S (T::*p)());
S operator()(T* p) const;
};
template <class S, class T, class A>
class mem_fun1_t : public binary_function<T*, A, S>;
adaptory wskaźników do metod – mem_fun_ref (C++98) , mem_fn (C++11)
Adaptor mem_fun_ref generuje obiekt funkcyjny, którego metoda jest wywoływany za
pomocą referencji do obiektu (czyli bezpośrednio na rzecz danego obiektu). Są wersje
dla metod z przydomkiem const i dla metod modyfikujących (poniżej bez const):
template<class S, class T>
mem_fun_ref_t<S,T> mem_fun_ref(S (T::*f)()); // bezargumentowa
template<class S, class T, class A>
mem_fun1_ref_t<S,T,A> mem_fun_ref(S (T::*f)(A)); // jednoargumentowa
Zwracany typ to:
template <class S, class T>
class mem_fun_ref_t : public unary_function<T, S>
{ public:
explicit mem_fun_ref_t(S (T::*p)());
S operator()(T& p) const;
};
template <class S, class T, class A>
class mem_fun1_ref_t : public binary_function<T, A, S>;
ptr_fun, mem_fun, mem_fun_ref – przykłady (C++98)
const char* tab[] = {"alice", "has", "a", "cat"};
replace_if( tab, tab+4,
not1(bind2nd(ptr_fun(strcmp), ”alice”)),”jarek” );
// przykład ryzykowny, jeśli łańcuch ”jarek” byłby dłuższy niż łańcuch ”alice”...
ostream_iterator<const char*> out (cout, " ");
copy(tab, tab+4, out); // jarek has a cat
struct A { void fun(int i) {cout<<"A::fun = "<< i <<endl;} };
struct B { int fun2(int i) const { return 2*i; } };
• bind może również
int x[] = {-4, -2, 0, 1, 3};
zastępować adaptory!
// za pomocą wskaźnika, czyli mem_fun
• jeśli nie ma argumentów
vector<A*> va;
to mem_fn wygodniejsze
va.push_back(new A);
va.push_back(new A); // hej, a kto to potem usunie? ;-)
for_each(va.begin(),va.end(),bind2nd(mem_fun(&A::fun), 5));
// za pomocą referencji do obiektu, czyli mem_fun_ref
vector<B> v;
v.push_back(B());
v.push_back(B()); // tu posprząta się samo...
transform(v.begin(), v.end(), x, ostream_iterator<int>(cout, " "), mem_fun_ref(&B::fun2) );
// -8 -4
bind zastępuje też adaptory (C++11)
class Klasa { public:
Klasa(int n) : val( n ) { }
void foo() { cout << val; }
void bar() const { cout << 10+val; }
private:
int val;
};
template<typename T>
struct pointer {
T* operator() (T& t) { return &t; }
};
Klasa tab[] = {9, 1, 8, 2, 7, 3, 6, 4, 5, 0};
// for_each(tab, tab+10, mem_fun_ref(&Klasa::foo)); // 9182736450
for_each( tab, tab+10, bind(&Klasa::foo,_1) ); // 9182736450
list<const Klasa*> lista;
transform(tab, tab+10, back_inserter(lista), pointer<Klasa>());
// for_each(lista.begin(), lista.end(), mem_fun(&Klasa::bar)); // 19111812171316141510
for_each(lista.begin(), lista.end(), bind(&Klasa::bar,_1)); // 19111812171316141510

Podobne dokumenty