Narzędzia użytkownika

Narzędzia witryny


lambda

To jest stara wersja strony!


Biblioteka Boost Lambda

Biblioteka standardowa 'algorithm' udostępnia wiele przydatnych szablonów umożliwiających wykonywanie często potrzebnych algorytmów na kontenerach. Konstrukcje te umożliwiają np. wyszukiwanie, sortowanie, przeglądanie z modyfikacją itp. Problem w tym, że dla każdej operacji trzeba było implementować drobną klasę tzw.obiekt funkcyjny. Obiekt ten miał za zadanie dostarczyć odpowiedniego operatora czy funkcji która miała za zadanie określić logikę operacji na elementach kontenera. Następnie obiekt był przekazywany do szablonu. Taka metoda powodowała powstanie wiele obiektów funkcyjnych które niekiedy w dużym projekcie były wykorzystywane tylko raz. Biblioteka boost wprowadza mechanizm (wraz z mechanizmami pomocniczymi) który umożliwia konstruowanie tak zwanych wyrażeń lambda. Można je rozumieć jako nienazwane funkcje które są wykonywane w miejscu definicji.

Zalety

Biblioteka boost charakteryzuje się następującymi zaletami:

  • mniejsza ilość kodu.
  • skupienie kodu w miejscu faktycznego wykorzystania.
  • łatwość konserwacji kodu (nie trzeba badać szeregu obiektów funkcyjnych)
  • zmniejszenie zapotrzebowania pamięci przez program.

Wady

Biblioteka boost charakteryzuje się następującymi wadami:

  • trudna i z początku ni jak nie intuicyjna składnia.
  • drobna pomyłka programisty to szereg błędów i ostrzeżeń generowanych przez kompilator (efekt zaawansowanych mocno zagłębionych szablonów).
  • zbyt „fachowe” podejście do tworzenia wyrażeń może znacznie utrudnić zrozumienie kodu przez innych programistów.

Przykłady

Poniższe przykłady pokazują (w niewielkim stopniu) możliwości tych wyrażeń. Należy w tym miejscu zwrócić uwagę, że często wyrażenia lambda wykorzystują mechanizmy pomocnicze zdefiniowane w innych bibliotekach których nagłówki należy dołączyć. Standardowo dołączamy bibliotekę boost\lambda\lambda.hpp. W przykładach będą pokazane nagłówki które dodatkowo należy dołączyć by kod się skompilował.

Pierwszy rzut oka na boost::lambda

W wyrażeniach lambda argumenty oznaczamy jako _X gdzie X może być cyfrą od 1 do 9. Bibloteka boost umożliwia zmianę nazwy _X na dowolną poprzez użycię boost::lambda::placeholderX_type. Jednak nie zaleca sie tego robić ze względu na możliwość skonfudowania programistów uczestniczących w projekcie, a przyzwyczajonych do standardowego oznaczenia.

#include <boost/lambda/lambda.hpp>
 
using namespace boost::lambda;
 
(std::cout << _2 << " " << _1 << " " << _3 << "\n")	      // Definicja wyrażenia 
("zadna lala,","Zadna panna,", "nie zastapi terminala!\n");   // Argumenty wywołania.

Kod ten możemy odczytać jako: Wywołaj w tym konkretnym miejscu funkcję która wyprowadzi na standardowe wyjście argumenty w kolejności 2,1,3.

Elementy kontenerów

Jednym z głównych powodów stworzenia boost::lambda było umożliwienie szybszego kodowania operacji na kontenerach. Załóżmy, że mamy kolekcje obiektów i chcemy na każdym z nich wykonać funkcję. Funkcja ta dodatkowo będzie wykonywana w programie tylko z argumentami pochodzącymi z tego kontenera i tylko w jednym miejscu. Załóżmy, że mamy:

void funkcja_globalna (const float i) 
{
	std::cout << "\nvoid funkcja_globalna : " << i;
}
 
struct Example
{
	void funkcja_klasy (const float i) const
	{
		std::cout << "\nvoid Example::funkcja_klasy : " << i;
	}
 
	~Example(){};
};
 
std::vector<float> vec_test;

Dawniej problem trzeba było rozwiązać tak:

#include <algorithm>
#include <functional>
 
	Example ex;
	Example * ex_ptr = &ex;
 
	std::cout << "Uzycie std::ptr_fun dla wiazania funkcji globalnej.";
	std::for_each(vec_test.begin(), vec_test.end(), std::ptr_fun(funkcja_globalna));
 
	std::cout << "Wiazanie z metoda klasy za pomoca mem_fun_ref (dla obiektu ex).";
	std::for_each(vec_test.begin(), vec_test.end(), std::bind1st(std::mem_fun_ref(&example::funkcja_klasy), ex));
 
	std::cout << "Wiazanie z metoda klasy za pomoca mem_fun (dla wskazania do obiektu ex).";
	std::for_each(vec_test.begin(), vec_test.end(), std::bind1st(std::mem_fun(&example::funkcja_klasy), ex_ptr));

Przy używaniu metod z obecnego standardu musimy rozróżnić wiązania dla obiektów i dla wskazań na te obiekty. Wyrażenia boost::lambda oraz boost::bind „domyślają się” z czym mają do czynienia. Zatem powyższy kod możemy zastąpić nieco lepszym używającym boost:bind :

#include <algorithm>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
 
	Example ex;
	Example * ex_ptr = &ex;
 
	std::cout << "Uzycie boost::bind dla funkcji globalnych.";
	std::for_each(vec_test.begin(), vec_test.end(), bind(&funkcja_globalna,_1));
 
	std::cout << "Uzycie boost::bind dla funkcji obiektow klasy.";
	std::for_each(vec_test.begin(), vec_test.end(), bind(&example::funkcja_klasy, ex ,_1));
 
	std::cout << "Uzycie boost::bind dla funkcji wskazan do obiektow klasy.";
	std::for_each(vec_test.begin(), vec_test.end(), bind(&example::funkcja_klasy, ex_ptr ,_1));

Natomiast dzięki wyrażeniom lambda możemy ominąć pisanie klasy lub funkcji globalnej i zdefiniować przetwarzanie w miejscu wołania:

#include <algorithm>
#include <boost/lambda/lambda.hpp>
 
	Example ex;
	Example * ex_ptr = &ex;
 
        using namespace boost::lambda;
 
	std::cout << "Uzycie wyrazenia lambda dla obiektow (czy tez wskazan do nich) wprost z std::vector.\n";
	std::for_each(vec_test.begin(), vec_test.end(), std::cout << constant("Wyrazenie boost::lambda: ") <<_1 << constant("\n")  );

W kodzie powyżej została użyta funkcja constant() która przekształca łańcuch tekstu na postać rozumianą przez wyrażenie lambda.

Mając przykładowy wektor vec_test chcemy posortować jego elementy malejąco. Dzięki wyrażeniom lambda nie musimy tworzyć obiektów funkcyjnych. Możemy warunek napisać wprost:

#include <algorithm>
#include <boost/lambda/lambda.hpp>
 
        using namespace boost::lambda;
 
	std::sort(vec_test.begin(), vec_test.end(), (_1 > _2) );
 
	std::cout << "Posortowany vector : \n";
	std::for_each(vec_test.begin(), vec_test.end(), std::cout << constant("Wyrazenie boost::lambda: ") <<_1 << constant("\n")  );

Konstrukcje sterujące oraz pętle

Przekazywane do szablonów standardowych algorytmów funkcje są zazwyczaj bardziej skomplikowane. Biblioteka boost::lambda umożliwia standardowe konstrukcje sterujące wymienione poniżej.

if_else

#include <algorithm>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/if.hpp>
 
        using namespace Boost::lambda;
 
	std::cout << "Uzycie konstrukcji if_else_then(warunek, instrukcje_dla_true, instrukcje_dla_false)." 
                  << "Alternatywnie if_(warunek)[instrukcje_dla_true].else_[instrukcje_dla_false].\n";
 
	for_each(vec_test.begin(), vec_test.end(), 
		if_then_else(_1 > 3 , 
		std::cout << constant("\n(if_then_else) Liczba : ") << _1 << constant( " jest wieksza niz 3!\n") ,
		std::cout << constant("\n(if_then_else) Liczba : ") << _1 << constant( " jest mniejsza lub rowna 3!\n")));

Switch

#include <boost/lambda/lambda.hpp>
#include <boost/lambda/switch.hpp>
 
        using namespace Boost::lambda;
 
	 std::cout << "Uzycie konstrukcji switch.\n"   ;
	 (switch_statement(
		 _1,
		 case_statement<0>
		 (std::cout << constant("Wybrales 0 \n")),
		 case_statement<1>
		 (std::cout << constant("Wybrales 1 \n")),
		 default_statement
		 (std::cout << constant("Nie mam pojecia co wybrales!\n")))
	 ) ((make_const(666)));

Użycie (make_const(666)) jest konieczne. Szablon tej funkcji jest następujący i jest zdefiniowany w bibliotece boost:

template <class T> inline const T&  make_const(const T& t) { return t; }

Przekazanie samego 666 mogłoby sprowokować błąd ponieważ 666 jest typu int i nie może mieć kwalifikatora const. Szablon natomiast potrzebuje const &.

Petla while

#include <boost/lambda/lambda.hpp>
#include <boost/lambda/loops.hpp>
 
        using namespace Boost::lambda;
 
	 std::cout << "Uzycie konstrukcji (while_loop(warunek, instrukcje))(argumenty)\n\n";
 
	 int start = 5;
	 int end = 10;
	 (while_loop(_1 <= _2, 
		 (++_1, std::cout  << _2 << constant(" - ") << _1 << constant(" = ") << _2 -_1 << constant("\n") )))
		 (start,end);

Petla do while

#include <boost/lambda/lambda.hpp>
#include <boost/lambda/loops.hpp>
 
        using namespace Boost::lambda;
 
 
	 (do_while_loop( _1 == 0 , std::cout << constant ("Uzycie konstrukcji (do_while_loop(warunek, instrukcje))(argumenty).\n")))(make_const(1));
 
	 (do_[
		 _1 == 0 , std::cout << constant ("Uzycie konstrukcji (do_[instrukcje].while_(warunek))(argumenty).\n")
		 ].while_(_1 == 0))(make_const(1));

Petla for

#include <boost/lambda/lambda.hpp>
#include <boost/lambda/loops.hpp>
 
        using namespace Boost::lambda;
 
	 std::cout << "Uzycie konstrukcji (for_ (war_poczatkowe,warunek, instrukcja iteracji)[instrukcje])(argumenty) : \n";
	 int i = 0;
 
	 (for_ (var(i)=0 , var(i) < _1, ++var(i))
		 [
			 var(std::cout) << var(i) << constant("^2 = ") << make_const( var(i)*var(i) ) << constant("\n")   
		 ]
	 )(make_const (4));

Rzutowanie i wyjątki

Biblioteka boost::lambda umożliwia wprowadzenie rzutowania w wyrażeniu lambda jak również genrowanie i przechwytywanie wyjątków. Oto przykład prezentujący konstrukcję:

#include <boost/lambda/lambda.hpp>
#include <boost/lambda/casts.hpp>
#include <boost/lambda/exceptions.hpp>
 
struct ExampleDerived : public Example 
{
	void funkcja_klasy_pochodnej (float i) const
	{
		std::cout << "void ExampleDerived::funkcja_klasy_pochodnej : " << i << std::endl;
	}
};
 
        using namespace Boost::lambda;
 
	 ExampleDerived ex_derv;								    
 
	 // Rzutowanie w tym przykładzie nie powiedzie się i zostanie wygenerowany wyjątek, a następnie 
	 // przechwycony i obsłużony.
 
	 std::cout << "Uzycie rzutowania i wykorzystanie mechanizmu wyjatkow w wyrazeniach lambda.\n";
	 (try_catch(
		 bind(&example_derived::funkcja_klasy_pochodnej, ll_dynamic_cast<example_derived&>(*_1),_2),
		 catch_exception<std::bad_cast>(bind(&example::funkcja_klasy,_1,_2))))(ex_ptr, make_const(66.6f));	
 
	 std::cout << std::endl;
	 // Rzutowanie w tym przykładzie powiedzie się. Przedstawiono dodatkowo schemat zapisu przechwytywania
	 // wyjątków różnych typów oraz dowolonego wyjątku.
 
	 (try_catch(
		 bind(&example_derived::funkcja_klasy_pochodnej, ll_dynamic_cast<example_derived&>(*_1),_2),
		 catch_exception<std::bad_cast>(bind(&example::funkcja_klasy,_1,_2)),
		 catch_exception<std::exception>(),
		 catch_all()))(make_const(&ex_derv), make_const(77.7f));

Plik z przykładami

Powyższe przykłady można wypróbować pobierając plik: lambda.cpp

lambda.1208381078.txt.gz · ostatnio zmienione: 2008/04/16 23:24 przez przemo86