rafallauterbach Temat założony przez niniejszego użytkownika |
Efektywne użycie lambd i std::function do wygodnego modyfikowania metod obiektu. » 2019-03-15 05:25:26 Witam, zamieniłem niedawno moje wirtualne funkcje obiektów na wskaźniki do funkcji, żeby móc łatwo nadpisać reakcję na event (zamiast tworzyć osobnej klasy dla każdego przycisku, to po prostu przypisuję inną funkcję). O lambdach słyszałem w przeszłości, ale dopiero teraz poczytałem o nich i pierwszy raz ich użyłem. Mam jednak problem. Chciałem stworzyć "domyślne" wartości dla funkcji, żeby móc zrobić coś jak wirtualne funkcje (zawołać najpierw metodę subklasy, a później klasy-rodzica). Zrobiłem teraz na szybko stałe lambdy (które przypisuje obiektowi przy initializacji) za pomocą "#define @lambda" , ale nie podoba mi się to, zaśmiecam namespace-a i mam to dostępne poza "Object"-em. Chcę więc przepisać kod na coś ładniejszego, ale pojawił się nowy problem - nie wiem jak zdefiniować prywatny stały statyczny wskaźnik do funkcji i zainicjalizować go. Szukam czegoś jak "design pattern" dla takiej sytuacji, bo kombinuję jak koń pod górkę. W sumie - chciałbym osiągnąć, coś co działało by tak : .h Class Object { public: void onMouseDown( const SDL_Event & ); void onMouseUp( const SDL_Event & ); void set_onMouseDown( std::function < void( const SDL_Event & ) > ); void set_onMouseUp( std::function < void( const SDL_Event & ) > ); void virtualize_onMouseDown( std::function < void( const SDL_Event & ) > ); void virtualize_onMouseUp( std::function < void( const SDL_Event & ) > ); private: std::function < void( const SDL_Event & ) > FonMouseDown; std::function < void( const SDL_Event & ) > FonMouseUp; static const std::function < void( const SDL_Event & ) > obj_default_onMouseDown =[]() { printf( "Click" ); }; static const std::function < void( const SDL_Event & ) > obj_default_onMouseUp =[]() { printf( "unClick" ); }; }
.cpp const std::function < void( const SDL_Event & ) > obj_default_onMouseDown const std::function < void( const SDL_Event & ) > obj_default_onMouseUp Object::Object() : FonMouseDown( obj_default_onMouseDown ) , FonMouseUp( obj_default_onMouseUp ) { } void Object::onMouseDown( const SDL_Event & e ) { return FonMouseDown( e ); } void Object::onMouseUp( const SDL_Event & ) { return FonMouseUp( e ); } void Object::set_onMouseDown( std::function < void( const SDL_Event & ) > new_func ) { FonMouseDown = new_func; } void Object::set_onMouseUp( std::function < void( const SDL_Event & ) > new_func ) { FonMouseUp = new_func; } void Object::virtualize_onMouseDown( std::function < void( const SDL_Event & ) > new_func ) { FonMouseDown =[ & ]( const SDL_Event & e ) { new_func( e ); obj_default_onMouseDown( e ); }; } void Object::virtualize_onMouseUp( std::function < void( const SDL_Event & ) > new_func ) { FonMouseUp =[ & ]( const SDL_Event & e ) { new_func( e ); obj_default_onMouseUp( e ); }; }
Nie wiem czy idę w dobrą czy w złą stronę, ale jeśli później mam móc łatwo przypisaywać sobie zmienne, to nie zaszkodzi mi raz napisać dużo linijek kodu, więc na razie pójdę tą drogą. Edit : teraz zamiast stałych statycznych lambd używam po prostu zwykłej metody. Nie było potrzeby, żeby robić z tego lambdę. Nadal pracuję jednak nad funkcją do modyfikowania zachowania obiektu i ewentualnym uproszczeniem całego kodu. |
|
rafallauterbach Temat założony przez niniejszego użytkownika |
update i pewien sukces » 2019-03-15 06:43:20 Mam teraz dla każdej z 5 potrzebnych mi metod 4 linijki kodu w .h i ... dużo linijek kodu w .cpp . .h public:
void draw( SDL_Renderer * ); void set_draw( std::function < void( SDL_Renderer * ) >, bool );
private:
std::function < void( SDL_Renderer * ) > f_draw; void base_draw( SDL_Renderer * );
.cpp
void Object::base_draw( SDL_Renderer * renderer ) { if( Object::texture != nullptr ) Object::texture->render( Object::placement, renderer ); if( Object::overlay != nullptr ) Object::overlay->render( Object::placement, renderer ); for( Object * obj: Object::children ) { obj->draw( renderer ); } }
void Object::set_draw( std::function < void( SDL_Renderer * ) > f, bool virt ) { if( !virt ) { f_draw = f; } else { f_draw =[ = ]( SDL_Renderer * rnd ) { f( rnd ); base_draw( rnd ); }; } }
void Object::draw( SDL_Renderer * renderer ) { if( f_draw != nullptr ) f_draw( renderer ); else base_draw( renderer ); }
i użycie :
temp =[ & OTemp ]( SDL_Renderer * ) { printf( "zmodyfikowany print \n" ); }; OTemp->set_draw( temp, true );
daje mi efekt, którego oczekiwałem. Jednak jakby miał ktoś sposób na mniejszy (i może "bezpieczniejszy") .h i .cpp dla klasy Object, to byłbym bardzo zainteresowany, bo na razie bezpieczeństwem tych metod/funkcji w ogóle się nie przejmowałem |
|
DejaVu |
» 2019-03-15 10:12:25 Jeżeli chcesz mieć eventy to utwórz sobie własny szablon, który potem będziesz wielokrotnie reużywał. Przykład: Events < paramrtry_metody > onClickEvents;
W C++11 można nawet zrobić to prościej. Przykład: float x_off = 0.f; auto makeBarButton =[ this, & x_off ]( const wchar_t * _name, float _width ) { auto tmp = createChild < ddt::gui::Button >(); tmp->setText( _name ); tmp->setWindowRect( sf::FloatRect( x_off, 0, _width, MenuHeight ) ); x_off += _width + 2; return tmp; };
makeBarButton( L"Open", 50.f )->setOnExecute( std::bind( & AppChartViewer::onOpenFilePressed, this ) ); makeBarButton( L"Prev (PgUp)", 90.f )->setOnExecute( std::bind( & AppChartViewer::onPrevFilePressed, this ) ); makeBarButton( L"Next (PgDn)", 90.f )->setOnExecute( std::bind( & AppChartViewer::onNextFilePressed, this ) ); x_off += 20.f; makeBarButton( L"Zoom out", 70.f )->setOnExecute([ this ]() { mref_chart->zoomOut(); } ); makeBarButton( L"Zoom in", 70.f )->setOnExecute([ this ]() { mref_chart->zoomIn(); } );
void Button::setOnExecute( const std::function < void() >& _delegate ) { m_onExecute = _delegate; }
void Button::updateMouse( const gfx::Mouse & _mc ) { if( m_bEnableMouseClick && _mc.wasReleased( sf::Mouse::Left ) && bHover ) { if( m_onExecute ) m_onExecute(); } }
void AppChartViewer::onOpenFilePressed() { }
|
|
rafallauterbach Temat założony przez niniejszego użytkownika |
» 2019-03-15 11:09:14 Czyli muszę chyba znowu poczytać o template'ach chyba jeśli chciałbym upięknić to co teraz zrobiłem A żeby nie musieć dużo pisać użyłem przed chwilą znowu #define ów... Taka pętla, zacząłem od #define i na nim skończyłem W sumie to o ile szablony nie dadzą mi jakiejś dużej przewagi, to chyba przy tym zostanę, zwłaszcza, że #define w .cpp nawet nie zaśmieca mi ogólnego namespace'a. teraz mam takie coś i działa cudownie, jako że spełnia wszystko, co wymagałem i nie trzeba dużo pisać Class Object { (......) #define GENERATE_DEFS(x,y) \ public : \ void x ( y ); \ void set_##x (std::function <void( y )> , bool virt = false); \ private : \ std::function<void( y )> f_##x = nullptr; \ void base_##x ( y ); GENERATE_DEFS( draw, SDL_Renderer * ) GENERATE_DEFS( onClick, const SDL_Event & ) GENERATE_DEFS( onMouseUp, const SDL_Event & ) GENERATE_DEFS( onMouseDown, const SDL_Event & ) GENERATE_DEFS( onMouseDrag, const SDL_Event & ) }
(......)
void Object::base_draw( SDL_Renderer * renderer ) { if( Object::texture != nullptr ) Object::texture->render( Object::placement, renderer ); if( Object::overlay != nullptr ) Object::overlay->render( Object::placement, renderer ); for( Object * obj: Object::children ) { obj->draw( renderer ); } }
void Object::base_onClick( const SDL_Event & e ) { ; }
void Object::base_onMouseUp( const SDL_Event & e ) { for( Object * child: children ) { if( child->cover( e.button.x, e.button.y ) ) { child->onMouseUp( e ); if( pressed == child ) child->onClick( e ); } } pressed = nullptr; }
void Object::base_onMouseDown( const SDL_Event & e ) { for( Object * child: children ) { if( child->cover( e.button.x, e.button.y ) ) { pressed = child; child->onMouseDown( e ); } } }
void Object::base_onMouseDrag( const SDL_Event & e ) { for( Object * child: children ) { if( child->cover( e.button.x, e.button.y ) &( child == pressed ) ) { child->onMouseDrag( e ); } } }
#define GENERATE_SET_CALL(x,y) \ \ void Object::set_##x (std::function <void(y e)>f, bool virt)\ { \ if (!virt) \ { \ f_##x = f; \ } \ else \ { \ f_##x = [=](y e) \ { \ f(e); \ base_##x (e); \ }; \ } \ } \ void Object::x (y e) \ { \ if (f_##x !=nullptr) \ f_##x (e); \ else \ base_##x (e); \ }
GENERATE_SET_CALL( onClick, const SDL_Event & ) GENERATE_SET_CALL( onMouseUp, const SDL_Event & ) GENERATE_SET_CALL( onMouseDown, const SDL_Event & ) GENERATE_SET_CALL( onMouseDrag, const SDL_Event & ) GENERATE_SET_CALL( draw, SDL_Renderer * )
OFF TOPIC A cała linia eventów u mnie w sumie wygląda w uproszczeniu mniej więcej tak Client ma vector Window (trochę przesadziłem, bo nie używam w ogóle wielu okien, ale było to proste w zaimplementowaniu i chciałem sobie tak zrobić) Window ma mapę name/Texture GUI ma mapę name/Scene Sceny mają vector Obiektów i zarazem są obiektem (tylko ich parent to nullptr) Window ma swoje GUI (Na razie w GUI includuję sceny, w których hardcoduję reakcję na eventy, obiekty które zawiera i co ma jaką teksturę.) (I nie wiem w sumie, czy potrzebuję to zmieniać, chyba tak zostanie) Window ma pointer do aktualnej sceny i tylko tą wyświetla w loopie w draw() Window w loopie ma SDL_PoolEvents(), Window decyduje sobie co zrobić z eventem przed wysłaniem do sceny (w sumie tylko w wypadku SDL_QUIT) Window woła aktualną scenę->process event Scena decyduje co zrobić z eventem i woła np swoje onMouseDown, co powoduje, że wszystkie obiekty, w które nacelowana jest myszka otrzymują event. Ale jak chodzi o clicked, to nie są wołane wszystkie buttony, tylko ten najbardziej na wierzchu... (Przynajmniej tak będzie jak potestuję i ponaprawiam kod). |
|
pekfos |
» 2019-03-15 11:50:56 Makra to niezbyt ładne rozwiązanie. Powinieneś raczej myśleć jak zredukować ilość powtarzanej logiki, zamiast szukać łatwiejszego sposobu na jej powtarzanie. Np mógłbyś mieć klasę, której obiekt przyjmuje 2 obiekty std::function, i wywołuje je tak jak draw i inne. W praktyce zamiast mieć metody set i call, mógłbyś po prostu trzymać te obiekty jako składowe publiczne: obiekt.draw(...); obiekt.draw.set([](...) {...} ); Wtedy nie musisz generować żadnego kodu. |
|
rafallauterbach Temat założony przez niniejszego użytkownika |
» 2019-03-15 12:40:53 Nie wiem jak to zrobić tak jak to opisałeś, ale spróbowałem zrobić enum i vector z funkcjami. Wrzuciłem dla testu obok tego co już mam - takie coś :
enum func { fdraw, fonClick, fonMouseUp, fonMouseDown, fonMouseDrag }; std::vector < std::function < void( const SDL_Event & ) > > fnc;
temp =[ & OTemp ]( const SDL_Event & ) { }; OTemp->fnc[ Object::fonClick ] = temp;
Co ciekawe nawet nie tknąłem Object.cpp(!) Tylko mam segfaulta już przy samym przypisaniu tej funkcji debugger wskazuje miejsce
template < typename _Tp > inline void swap( _Tp & __a, _Tp & __b ) #if __cplusplus >= 201103L noexcept( __and_ < is_nothrow_move_constructible < _Tp >, is_nothrow_move_assignable < _Tp >>::value ) #endif { __glibcxx_function_requires( _SGIAssignableConcept < _Tp > ) _Tp __tmp = _GLIBCXX_MOVE( __a ); -----TO------_ > __a = _GLIBCXX_MOVE( __b ); __b = _GLIBCXX_MOVE( __tmp ); }
Callstack : #0 0x40be7a std::swap<std::_Any_data>(__a=..., __b=...) (/usr/include/c++/5/bits/move.h:186) #1 0x40b68b std::function<void (SDL_Event const&)>::swap(std::function<void (SDL_Event const&)>&)(this=0x7fffffffde30, __x=...) (/usr/include/c++/5/functional:2156) #2 0x40b101 std::function<void (SDL_Event const&)>::operator=(std::function<void (SDL_Event const&)> const&)(this=0x0, __x=...) (/usr/include/c++/5/functional:2071) #3 0x40f57b GUI::init(this=0x90fa40, textures=..., fonts=...) (/home/rafal/Projects/CPP/SDL/hexrex/Client/src/game/GUI/GUI_Scenes.cpp:67) #4 0x403a62 Client::init_scenes(this=0x633470) (/home/rafal/Projects/CPP/SDL/hexrex/Client/src/Client.cpp:69) #5 0x403937 Client::init(this=0x633470) (/home/rafal/Projects/CPP/SDL/hexrex/Client/src/Client.cpp:59) #6 0x404034 Client::run(this=0x633470) (/home/rafal/Projects/CPP/SDL/hexrex/Client/src/Client.cpp:121) #7 0x403447 main() (/home/rafal/Projects/CPP/SDL/hexrex/Client/main.cpp:64)
Edit : Zamieniłem tylko vector na map<func, i działa. Najwyraźniej te 2 "rzeczy" różnią się od siebie bardziej niż tylko nazwą i konwertowaniem indeksu. |
|
« 1 » |