11 - Functors y lambda functions ...

¿Qué es un functor?

Un functor es una clase que tiene definido el operator() como member function. Por ejemplo, observa la clase amAFunctor.

class amAFunctor {
public:
  void operator()() {
    printf("Hello functor\n");
  };
};

Al tener definido el operator(), dado un objeto f de la clase amAFunctor, podemos aplicarle el operator ().

int main()
{
    amAFunctor f;   // declaración de objeto f
    f();            // Imprime "hello functor"

    return 0;
}

He aquí el resultado de compilar ese programa. Nota que el compilador creo la member function operator()() para la clase amAFunctor.

"amAFunctor::operator()()":
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     QWORD PTR [rbp-8], rdi
        mov     edi, OFFSET FLAT:.LC0
        call    "puts"
        nop
        leave
        ret
"main":
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        lea     rax, [rbp-1]
        mov     rdi, rax
        call    "amAFunctor::operator()()"
        mov     eax, 0
        leave
        ret

¿Cómo se relacionan las funciones lambda a los functors?

Las funciones lambda (tambien conocidas como funciones anónimas) en C++ son convertidas por el compilador en functors! Mira en lo que se convierte este programa:

int main()
{
    auto amALambda = []() {
        printf("Hello lambda\n");
    };

    amALambda();
    return 0;
}

Exactamente en lo que se había convertido con el functor! (con excepción de que la clase usa otro nombre, el compilador le llamó lambda a esa clase).

"main::'lambda'()::operator()() const":
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     QWORD PTR [rbp-8], rdi
        mov     edi, OFFSET FLAT:.LC0
        call    "puts"
        nop
        leave
        ret
"main":
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        lea     rax, [rbp-1]
        mov     rdi, rax
        call    "main::'lambda'()::operator()() const"
        mov     eax, 0
        leave
        ret

Las funciones lambda tienen la capacidad de capturar variables locales de la función donde son invocadas. Por ejemplo:

int main()
{
    int n = 8;
    auto amALambdaWCapture = [&]() {
        printf("Hello lambda %d\n", n);
    };

    amALambdaWCapture();    // Imprime "Hello lambda 8"

    n = 42;

    amALambdaWCapture();    // Imprime "Hello lambda 42"

    return 0;
}

Es compilado a:

01 "main::'lambda'()::operator()() const":
02      push    rbp
03      mov     rbp, rsp
04      sub     rsp, 16
05      mov     QWORD PTR [rbp-8], rdi
06      mov     rax, QWORD PTR [rbp-8]
07      mov     rax, QWORD PTR [rax]
08      mov     eax, DWORD PTR [rax]
09      mov     esi, eax
10      mov     edi, OFFSET FLAT:.LC0
11      mov     eax, 0
12      call    "printf"
13      nop
14      leave
15      ret
16  "main":
17      push    rbp
18      mov     rbp, rsp
19      sub     rsp, 16
20      mov     DWORD PTR [rbp-4], 8.      ; n = 8
21      lea     rax, [rbp-4]               ; rax = &n 
22      mov     QWORD PTR [rbp-16], rax.   ; [rbp-16] = &n
23      lea     rax, [rbp-16]              ; rax = rbp-16
24      mov     rdi, rax                   ; primer arg = dirección de obj
25      call    "main::'lambda'()::operator()() const"
26      mov     DWORD PTR [rbp-4], 42
27      lea     rax, [rbp-16]
28      mov     rdi, rax
29      call    "main::'lambda'()::operator()() const"
30      mov     eax, 0
31      leave
32      ret

Observaciones:

Qué significa para reverse engineering

El efecto de usar lambda functions lo experimentamos cuando analizamos un programa de Qt que las usaba:

  1  #include <QApplication>
  2  #include <QMainWindow>
  3  #include <QMenuBar>
  4  #include <QAction>
  5  #include <QMessageBox>
  6  
  7  int main(int argc, char *argv[])
  8  {
  9      QApplication app(argc, argv);
 10      QMainWindow w;
 11  
 12      int cnt = 0;
 13      QMenu *file = w.menuBar()->addMenu(QObject::tr("&File"));
 14      QAction *hello = file->addAction(QObject::tr("&Say Hello"));
 15      QAction *info = file->addAction(QObject::tr("&Info"));
 16      QAction *quit  = file->addAction(QObject::tr("&Quit"));
 17  
 18      // CONECTAR A SAY HELLO
 19      QObject::connect(hello, &QAction::triggered, [&]{
 20          QString message = "Not yet!";
 21          if (cnt > 2) message = "Hello, World!";
 22          QMessageBox::information(&w, message, message);
 23      });
 24      
 25      // CONECTAR A INFO
 26      QObject::connect(info, &QAction::triggered, [&]{
 27          cnt++;
 28          QMessageBox::information(&w, "Info", QString::number(cnt));
 29      });
 30      
 31      QObject::connect(quit, &QAction::triggered, &app, &QApplication::quit);
 32  
 33      w.resize(400, 200);
 34      w.show();
 35      return app.exec();
 36  }

La función principal del programa creaba las opciones del menú y conectaba una función con cada dos de las opciones (Say Hello e Info) (lineas 18-29).

Nota que en ambas conecciones se utiliza una función lambda. Por ejemplo la del primer connect es:

[&]{
        QString message = "Not yet!";
        if (cnt > 2) message = "Hello, World!";
        QMessageBox::information(&w, message, message);
    }

.. en otras palabras, una función lambda que NO recibe argumentos (no tiene “( )”). El “[&]” es n lambda capture clause y significa que esa función tendrá acceso a las variables locales de la función en la que se encuentra (en este caso main)

Esta es la descompilación de esa función lambda:

  1  void __thiscall
  2  QtPrivate::detail::StorageByValue<>::anon_struct_16_2_0337a457_for_o::operator()
  3            (anon_struct_16_2_0337a457_for_o *this)
  4  
  5  {
  6    undefined8 *in_RCX;
  7    QString *pQVar1;
  8    QFlags<> *in_stack_ffffffffffffff98;
  9    StandardButton in_stack_ffffffffffffffa0;
 10    QString message;
 11    uint local_1c;
 12    
 13                      /* Unresolved local var: QMainWindow * w@[???] */
 14    QString::QString(&message,"Not yet!");
 15    if (2 < *(int *)*in_RCX) {
 16      QString::operator=(&message,"Hello, World!");
 17    }
 18    QFlags<>::QFlags(in_stack_ffffffffffffff98,in_stack_ffffffffffffffa0);
 19    pQVar1 = &message;
 20    QMessageBox::information
 21              ((QWidget *)in_RCX[1],(QString *)pQVar1,(QString *)&message,
 22               (QFlags *)(ulonglong)local_1c,NoButton);
 23    QString::~QString(&message,(int)pQVar1);
 24    return;
 25  }turn;
}

Observaciones: