Efektywne użycie lambd i std::function do wygodnego modyfikowania metod obiektu.
Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Zarejestruj się!

Efektywne użycie lambd i std::function do wygodnego modyfikowania metod obiektu.

AutorWiadomość
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 :
C/C++
//     PSEUDO KOD
.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.
P-174195
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 .

C/C++
.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 :

C/C++
//std::function <void(SDL_Renderer*) temp
//(...)
//Object* OTemp = new Object ( ... )
//(...)

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
P-174196
» 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:
C/C++
Events < paramrtry_metody > onClickEvents;

W C++11 można nawet zrobić to prościej. Przykład:
C/C++
// Tworzenie przycisków:
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 ) //override
{
    //...
   
    if( m_bEnableMouseClick && _mc.wasReleased( sf::Mouse::Left ) && bHover )
    {
        if( m_onExecute )
             m_onExecute();
       
    }
   
    //..
}


//...
void AppChartViewer::onOpenFilePressed()
{
    //...
}

P-174197
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ć

C/C++
// Object.h
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 & )
}

// Object.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::base_onClick( const SDL_Event & e )
{
    ; //do nothing.
}

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

P-174198
» 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:
C/C++
obiekt.draw(...);
obiekt.draw.set([](...) {...} );
Wtedy nie musisz generować żadnego kodu.
P-174199
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ś :

C/C++
//Object.h

enum func {
    fdraw,
    fonClick,
    fonMouseUp,
    fonMouseDown,
    fonMouseDrag
};
std::vector < std::function < void( const SDL_Event & ) > > fnc;

//Zawołanie :

temp =[ & OTemp ]( const SDL_Event & ) {
    /*SDL_Event tmpEvent;
                tmpEvent.type = SDL_QUIT;
                SDL_PushEvent(&tmpEvent);*/
};
OTemp->fnc[ Object::fonClick ] = temp; /*
SDL_Event e;
OTemp->fnc[Object::fonClick](e);*/

Co ciekawe nawet nie tknąłem Object.cpp(!)
Tylko mam segfaulta już przy samym przypisaniu tej funkcji

debugger wskazuje miejsce
C/C++
//Move.h

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
{
    // concept requirements
    __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.
P-174200
« 1 »
 Strona 1 z 1