Ładowanie
popup

C++11: Jak się pozbyć placeholderów?

2013-01-03 13:41:57

Bindując funkcje do obiektów std::function jesteśmy zmuszeni do podawania tzw. placeholderów. Choć niekiedy ich "mieszanie" może być przydatne, jednak zazwyczaj ustawia się je po kolei (_1, _2, _3, ...). Niestety nie ma prostego sposobu żeby pominąć tą czynność dla tych właśnie przypadków. Rozwiązaniem może być specjalna funkcja szablonowa, która po "wyliczeniu" ilości argumentów obiektu std::function (które są podawane jako argument szablonu) i dopasowaniu do niego odpowiedniej funkcji. Każda z tych funkcji różni się ilością placeholderów w argumentach funkcji std::bind.

template<typename S,typename I,typename FN>struct bind{ template<int N> static typename std::enable_if<N==0>::type get(S ptr,I *ref,FN &fp){fp=std::bind(ptr,ref);} template<int N> static typename std::enable_if<N==1>::type get(S ptr,I *ref,FN &fp){fp=std::bind(ptr,ref,std::placeholders::_1);} template<int N> static typename std::enable_if<N==2>::type get(S ptr,I *ref,FN &fp){fp=std::bind(ptr,ref,std::placeholders::_1,std::placeholders::_2);} ... };

Nie da się niestety napisać różnych wersji tej funkcji automatycznie, ale wątpię że ktokolwiek będzie miał potrzebę używania więcej niż kilku argumentów ;) Jak widać jest tu użyta klasa std::enable_if, (która też jest częścią nowego standardu). Jest to prosta klasa która pozwala wyeliminować funkcje których nie potrzebujemy w zależności od podanego warunku (kompilator na to pozwala, można poczytać o tym więcej tu SFINAE)

Dzięki takiej funkcji (i z pomocą C++11) można łatwo zbudować klasę eventów bardzo przypominającą tę z C# bez kombinowania z castowaniem wskaźników lub budowaniem jej od zera na template-ach. Dla uproszczenia podam prostą implementację klasy "delegate" korzystająca z tej funkcji:

template<typename... P> class delegate{ static const int argsCount=sizeof...(P); // tutaj wyliczamy ilość argumentów szablony przekazanych do klasy typedef std::function<void(P...)> tdSlot; // typ funkcji, dla uproszczenia zwraca void tdSlot m_slot; public: delegate(){} ~delegate(){} void connect(const tdSlot &f){ m_slot=f; } template<typename MP,typename TP>void connect(MP mp,TP *tp){ bind<MP,TP,tdSlot>::template get<argsCount>(mp,tp,m_slot); // po prostu wywołujemy odpowiednią funkcję w zależności od ilości argumentów } void fire(P... args){ m_slot(args...); // odpala delagata z podanymi argumentami (przekazuje je dalej) } void operator=(const tdSlot &f){return connect(f);} };

Mam w tej klasie 2 funkcje connect, pierwsza może służyć do bindowania lambdy/"zwykłej" funkcji lub przyjmowania rezultatu std::bind, druga służy do bindowania metod wewętrznych klas - pierwszy argument to wskaźnik do metody, druga to obiekt na którym wywołamy delegata. Prosty przykład użycia:

class foo{ public: void zzz(int &x){ std::cout<<"\nfoo::zzz="<<x; } void zzzff(float x,float y){ std::cout<<"\nfoo::zzzf="<<x<<", "<<y; } }; int main(int argc, char**argv){ foo obiekt; delegate<float,float> df; df.connect(&foo::zzzff,&obiekt); df.fire(5,10); df.connect(std::bind(&foo::zzzff,&obiekt,std::placeholders::_1,std::placeholders::_2)); // można też tak df.fire(10,15); int intArg=66; delegate<int&> di; di=[](int &o){std::cout<<"\ninside lambda: "<<o; ++o;}; di.fire(intArg); di.fire(intArg); std::cout<<"\nchanged value: "<<intArg; di.connect(&foo::zzz,&obiekt); di.fire(intArg); return 0; }

Tagi: Programowanie C++ Templates | Dodaj komentarz

Zabawa w DIY Audio

2012-04-20 12:45:47

Dziś nietypowy wpis (nie związany z programowaniem). Dodałem nowy projekt do działu Projekty - są to kolumny głośnikowe wykonane od zera. Więcej szczegółów znajdziecie w opisie projektu.

Tak wyglądają ;)
Tak wyglądają ;)

Przy okazji uzbroiłem stronę w galerię i wrzuciłem kilka zdjęć. Co prawda mogłem wykorzystać np. Picase, ale coś mnie podkusiło żeby napisać swój skrypt i nie odparłem się pokusie ;)

Galeria z projektem kolumn

Tagi: DIY Audio | Komentarze (1)

Przenoszę...

2011-04-22 14:07:27

... stronę tu: jakubduracz.com. Mimo że nie często tu piszę (a jeśli już), to obsługa/edycja wpisów czy szablonów na bloggerze mnie irytuje. Postawiłem na własny, minimalistyczny CMS z prostym językiem formatowania tekstu (WYSiWG to koszmar, a CMS-y pokroju Wordpress/Blogger/... to stanowczo overkill jak na moje potrzeby).

//~

Tagi: Inne | Komentarze (1)

Przyspieszanie Frustum Culling

2010-12-01 15:37:00

Dziś mały tip o optymalizacji Frustum Culling(zdefiniowany jest na 6 płaszczyznach) dla OBBox i AABBox (Prostopadłościan dowolnie zorientowany i 'wyrównany' do osi układu współrzędnych). Zacznę od struktury boxów:

class AABBox{ Vec3f min; // maksymalne wartości dla każdej z osi Vec3f max; // minimalne wartości dla każdej z osi }; class OBBox{ Vec3f center; // środek prostopadłościanu Vec3f axis[3]; // wektory opisujące układ współrzędnych Vec3f size; // długośći dla każdej z osi };

Najprostszym sposobem na sprawdzenie zawierania takich boxów w frustumie jest sprawdzenie zawierania każdego z jego wierzchołków dla każdej z 6 płaszczyzn frustuma. Żeby to troszkę przyspieszyć wyznaczam jeden wierzchołek (punkt p), który jest "najdalej" wysunięty względem normalnej danej płaszczyzny i sprawdzam relację tego punktu względem tej płaszczyzny. Jeśli dany wierzchołek znajduje się "nad" płaszczyzną - box znajduje się w zasięgu widzenia. Większość tego typu implementacji np. http://www.lighthouse3d.com/opengl/viewfrustum/index.php?gatest3 oblicza niepotrzebnie czy box przecina się z płaszczyzną - co narzuca niepotrzebny drugi test punkt-płaszczyzna i jest w większości przypadków niepotrzebne.

Wyliczanie punktu p dla AABBox:

// aabb - nasz box // normal - normalna płaszczyzny Point3f p=aabb.min; if(normal.x>=0) p.x=aabb.max.x; if(normal.y>=0) p.y=aabb.max.y; if(normal.z>=0) p.z=aabb.max.z;

Wyliczanie punktu dla OBBox:

// obb - nasz box // normal - normalna płaszczyzny // definiuje punkt poczatkowy (bedziemy go przesuwac wzgledem kazdej osi o odpowienie wartosci) Point3f p=obb.center; for(int i=0;i<3;++i){ float d=obb.axis[i]^normal; // projekcja normalnej wzdluz tej osi (operator ^ wykonuje iloczyn skalarny) // jesli d jest mniejsze od 0 - szukany punkt jest po negatywnej stronie danej osi (-axis[i]) if(d>0.f) p+=obb.axis[i]*obb.size[i]; else p-=obb.axis[i]*obb.size[i]; }

Dysponując takim punktem p, wystarczy sprawdzić czy znajduje się on "nad" płaszczyznami frustuma. Przykładowy kod sprawdzający:

// dla każdej płaszczyzny frustuma: for(int i=0;i<6;++i){ // m_planes - tablica z płaszczyznami if(!m_planes[i].Distance(p)>0) // sprawdzam czy punkt p znajduje się nad płaszczyzną return false; // jeśli nie - box napewno jest niewidoczny (można przerwać sprawdzanie) } return true;

Wykonałem prosty programik testowy, gdzie można sobie sprawdzić działanie tego kodu: frustm.culling.2.zip

Demo Frustum Culling...
Demo Frustum Culling...

Jak widać na tym screenie - przyrost wydajnosci jest, ale oczywiście nie ma większego sensu się bawić w takie optymalizacje - chyba że "dla sportu" ;)

Tagi: Programowanie Engine Optymalizacja | Komentarze (1)

Kod z przyszłości

2010-10-25 14:35:47

Wczoraj spotkała mnie ciekawa niespodzianka. Podczas testowania silnika przy każdej zmianie jednego pliku i próbie skompilowania i uruchomienia programu testowego (który znajduje się w tej samej solucji co silnik i jest zależny od silnika) prawie cały silnik kompilował się od początku. Opcja Minimal Build (czy coś takiego) była włączona... skasowanie plików tymczasowych nie pomogło (chyba że były jeszcze jakieś których nie zauważyłem), restart VS też nic, pozostało ręcznie kompilowanie modyfikowanego pliku i linkowanie zamiast po prostu wciśnięcia F5. Pomyślałem że mu przejdzie np. po restarcie komputera :] Przed wyłączeniem komputera i pójściem spać włączyłem backup dysku... dodam że mój backup kopiuje tylko pliki które mają "nowszą" datę modyfikacji. spojrzałem na log, w którym lista zaktualizowanych plików kopii była pusta, mimo że modyfikowałem kilka plików. Od razu popatrzyłem na da datę systemową, która wskazywała na 2006 rok i podobną godzinę do tej która wtedy była w rzeczywistości (sic!), przyczyną zmiany daty był podłączony dysk przenośny USB który nie wiem czemu przy każdym starcie komputera resetował datę (bez dysku już było ok). Po ustawieniu daty na prawidłową od razu uruchomiłem VS - tym razem IDE kompilowało tylko te pliki które były NAPRAWDĘ zmienione od ostatniej kompilacji, a nie te które tylko mają od tej daty kompilacji "nowszą" datę :)

PS. Wg. mnie Visual Studio powinien się połapać że data systemowa może być zła, skoro kompiluje pliki które zostały zmodyfikowane w "przyszłości".

Tagi: Inne Visual Studio | Dodaj komentarz

Method Chaining

2010-10-04 10:56:39

Dziś napiszę o często stosowanej przeze mnie (i ogólnie w programowaniu wysokopoziomowym) technice programowania, mianowicie - "Method Chaining". Główną jej zaletą jest zwiększenie czytelności kodu, który staje się czytelniejszy i po prostu krótszy. Dobry przykład zastosowania tej techniki znajdziemy w bibliotece jQuery, albo w standardowej bibliotece C++ (choć przez przeładowanie operatorów << i >> można jej od razu nie dostrzec). Polega ona na wywoływaniu kilku funkcji na rzecz danego obiektu w jednej instrukcji. np.:

obiekt.funkcja1().funkcja2().funkcja3();

Powyższy przykład bez zastosowania Method Chaining wyglądał by tak:

obiekt.funkcja1(); obiekt.funkcja2(); obiekt.funkcja3();

Działanie kodu jest proste: każda wywołana funkcja w "łańcuchu" zwraca jako rezultat adres/referencję do obiektu dla którego jest wywoływana (wskaźnik this). Poniżej inny przykład z jakiegoś mojego kodu:

// z wykorzystaniem method chaining geometry.Scale(vec_scale).Translate(trans_vec).Rotate(rotate_vec,rotate_angle).CalculateNormals().Apply(); // bez wykorzystania method chaning geometry.SetScale(vec_scale); geometry.SetTranslate(trans_vec); geometry.SetRotate(rotate_vec,rotate_angle); geometry.SetCalculateNormals();

Kod ten kolejno wykonuje jakieś operacje na modelu o nazwie geometry, czyli kolejno: zmienia skalę modelu, dokonuje transformacji, następnie jest rotacja o zadany kąt według jakiegoś wektora, na koniec wyliczenie wektorów normalnych i wykonanie wcześniej ustalonych obliczeń funkcją apply. Jak widać kod jest bardziej skondensowany i czytelniejszy, a samo jego pisanie jest szybsze i przyjemniejsze (zwłaszcza z pomocą narzędzi typu InteliSense). Przykładowa implementacja klasy Geometry może wyglądać tak:

class Geometry{ private: bool todo_calc_normals,todo_rotate,todo_translate,todo_scale; public: Geometry():todo_calc_normals(false),todo_rotate(false),todo_translate(false),todo_scale(false){} Geometry &CalculateNormals(){ todo_calc_normals=true; return *this; } Geometry &Scale(const Vec3f &iScale){ todo_scale=true; // tu zapisujemy jeszcze gdzies w pamieci wektory 'iScale' return *this; } Geometry &Translate(const Vec3f &iTranslation){ todo_translate=true; // zapisujemy w pamieci 'iTranslation' return *this; } Geometry &Rotate(const Vec3f &iRotation,float iAngle){ todo_rotate=true; // zapisujemy w pamieci wektor 'iRotation' i kat 'iAngle' return *this; } void Apply(){ // na podstawie zebranych danych obliczamy dodane zadania } };

Jak widać kod jest bardzo prosty - całą robotę wykonuje instrukcja return *this - czyli jak wspomniałem - zwracanie referencji do obiektu na którym dalej chcemy wywoływać następną funkcję. Należy pamiętać w powyższym kodzie o odpowiednim ustawieniu flag 'todo_*' i zapamiętywaniu danych wejściowych niezbędnych do zakończenia obliczeń. Dzięki funkcji Apply bardzo łatwo można wprowadzić spore optymalizacje: zamiast osobno przechodzić dane modelu dla policzenia kolejno kilku transformacji na modelu, można na raz dokonać obliczeń raz przechodząc dane.

Technika ta oczywiście nie ogranicza się tylko do wywoływania funkcji jednego obiektu - spore możliwości pokazuje gdy wywołujemy łańcuch metod na kilku różnych obiektach, wspaniale upraszczając kod. Na koniec zamieszczam jeszcze kilka imponujących przykładów z wykorzystaniem biblioteki jQuery które zainspirowały mnie do napisania tej notki:

$('ul.first') .find('.foo').css('background-color', 'red').end() .find('.bar').css('background-color', 'green').end();

Powyższy przykład szuka wszystkich pod-elementów wypunktowania ul o klasie first i zmienia kolor tła na czerwony dla tych z klasą foo i na zielony dla pod-elementów z klasą bar.

$('form#login') .find('label.optional').hide().end() .find('input:password').css('border', '1px solid red').end() .submit(function(){ return confirm('Are you sure you want to submit?'); });

Kod ten wybiera jakiś formularz do logowania (o identyfikatorze login), później ukrywa wszystkie labele o klasie optional tego formularza. Ustawia czerwoną ramkę na pole tekstowe hasła formularza i ustawia mu jeszcze callback wywoływany przy wysłaniu formularza.

Dodatkowo funkcja end pozwala na przedłużenie łańcucha metod cofając wskaźnik elementu na którym pracujemy zwracając referencję do swojego rodzica. Nie trzeba zapamiętywać żadnych wyszukanych zmiennych, czy wyszukiwać elementów kilka razy - wszystko jest wykonane za pomocą jednego łańcucha metod.

przykłady pochodzą z:

Tagi: Programowanie | Komentarze (2)

Preloading obrazków w jQuery

2010-09-29 10:34:04

Dziś mały tip Javascript/jQuery. Kod ten pozwala na wykrycie momentu załadowania do pamięci pobranego obrazka na stronie internetowej. Dodaje go tutaj bo trochę sie nad tym męczyłem, a może się komuś przydać (w programowaniu 'webowym' jestem nowicjuszem ;) ). Od razu przejdę do kodu i krótkiego omówienia co się w tym kodzie dzieje:

function load_image(imgSrc){ // przed ladowaniem obrazka (np. pojawia sie gif z ladowaniem) var image=new Image(); // (1) $(image).load(function(){ // (2) // obrazek zaladowany do pamieci (np. mozna schowac juz gif z ladowaniem) // laduje obrazek z pamieci do naszego elementu img (zmiana atrybutu src) $('#popup_image').attr('src',imgSrc); // (4) // np. pokazuje wczesniej ukryty obrazek $('#popup_image').fadeIn('slow'); // zmiana pozycji rozmiaru obrazka itp. ... // reset callbacka image.onload=function(){}; }).attr('src',imgSrc); // (3) }

PS. Przy okazji polecam wszystkim web-developerom bibliotekę jQuery, zwłaszcza często używających Javascript. Jest to kompaktowa i bardzo dobrze zaprojektowana biblioteka, bardzo przyjemna w obsłudze.

Tagi: Programowanie jQuery Javascript | Komentarze (2)

<< Starsze