c++ - Cómo funciona std:: bind con las funciones miembro
c++11 stdbind (2)
Cuando dice "el primer argumento es una referencia" seguramente quiso decir "el primer argumento es un
puntero
": el operador
&
toma la dirección de un objeto, produciendo un puntero.
Antes de responder esta pregunta, retrocedamos brevemente y veamos su primer uso de
std::bind()
cuando usa
std::bind(my_divide, 2, 2)
Usted proporciona una función. Cuando una función se pasa a cualquier lugar, se desintegra en un puntero. La expresión anterior es equivalente a esta, tomando explícitamente la dirección
std::bind(&my_divide, 2, 2)
El primer argumento para
std::bind()
es un objeto que identifica cómo llamar a una función.
En el caso anterior, es un puntero para funcionar con el tipo
double(*)(double, double)
.
Cualquier otro objeto invocable con un operador de llamada de función adecuado también funcionaría.
Dado que las funciones miembro son bastante comunes,
std::bind()
proporciona soporte para tratar las funciones de puntero a miembro.
Cuando usa
&print_sum
solo obtiene un puntero a una función miembro, es decir, una entidad de tipo
void (Foo::*)(int, int)
.
Si bien los nombres de las funciones se reducen implícitamente a punteros a funciones, es decir, se puede omitir
&
, lo mismo no es cierto para las funciones miembro (o miembros de datos, para el caso): para obtener un puntero a una función miembro es necesario utilizar el
&
.
Tenga en cuenta que un puntero al miembro es específico de una
class
pero se puede usar con cualquier objeto de esa clase.
Es decir, es independiente de cualquier objeto en particular.
C ++ no tiene una forma directa de obtener una función miembro directamente vinculada a un objeto (creo que en C # puede obtener funciones directamente vinculadas a un objeto mediante el uso de un objeto con un nombre de miembro aplicado; sin embargo, han pasado más de 10 años desde La última vez que programé un poco de C #).
Internamente,
std::bind()
detecta que se pasa un puntero a una función miembro y probablemente lo convierte en objetos invocables, por ejemplo, mediante el uso de
std::mem_fn()
con su primer argumento.
Dado que una función miembro no
static
necesita un objeto, el primer argumento para el objeto invocable de resolución es una referencia o un puntero [inteligente] a un objeto de la clase apropiada.
Para usar un puntero a la función miembro se necesita un objeto.
Cuando se usa un puntero al miembro con
std::bind()
el segundo argumento para
std::bind()
debe especificar de manera correspondiente cuándo proviene el objeto.
En tu ejemplo
std::bind(&Foo::print_sum, &foo, 95, _1)
el objeto invocable resultante usa
&foo
, es decir, un puntero a
foo
(de tipo
Foo*
) como el objeto.
std::bind()
es lo suficientemente inteligente como para usar cualquier cosa que parezca un puntero, cualquier cosa convertible en una referencia del tipo apropiado (como
std::reference_wrapper<Foo>
), o una [copia] de un objeto como objeto cuando El primer argumento es un puntero al miembro.
Sospecho que nunca has visto un puntero al miembro; de lo contrario, sería bastante claro. Aquí hay un ejemplo simple:
#include <iostream>
struct Foo {
int value;
void f() { std::cout << "f(" << this->value << ")/n"; }
void g() { std::cout << "g(" << this->value << ")/n"; }
};
void apply(Foo* foo1, Foo* foo2, void (Foo::*fun)()) {
(foo1->*fun)(); // call fun on the object foo1
(foo2->*fun)(); // call fun on the object foo2
}
int main() {
Foo foo1{1};
Foo foo2{2};
apply(&foo1, &foo2, &Foo::f);
apply(&foo1, &foo2, &Foo::g);
}
La función
apply()
simplemente obtiene dos punteros a los objetos
Foo
y un puntero a una función miembro.
Llama a la función miembro señalada con cada uno de los objetos.
Este operador divertido
->*
está aplicando un puntero a un miembro a un puntero a un objeto.
También hay un operador
.*
Que aplica un puntero a un miembro a un objeto (o, como se comportan como objetos, una referencia a un objeto).
Como un puntero a una función miembro necesita un objeto, es necesario usar este operador que solicita un objeto.
Internamente,
std::bind()
organiza lo mismo para que suceda.
Cuando se llama a
apply()
con los dos punteros y
&Foo::f
se comporta exactamente igual que si se llamara al miembro
f()
en los objetos respectivos.
Del mismo modo, cuando se llama a
apply()
con los dos punteros y
&Foo::g
se comporta exactamente igual que si se llamara al miembro
g()
en los objetos respectivos (el comportamiento semántico es el mismo pero es probable que el compilador tenga mucho es más difícil alinear funciones y, por lo general, no lo hace cuando se utilizan punteros para los miembros).
Estoy trabajando con
std::bind
pero aún no entiendo cómo funciona cuando lo usamos con funciones de clase miembro.
Si tenemos la siguiente función:
double my_divide (double x, double y) {return x/y;}
Entiendo perfectamente bien las siguientes líneas de código:
auto fn_half = std::bind (my_divide,_1,2); // returns x/2
std::cout << fn_half(10) << ''/n''; // 5
Pero ahora, con el siguiente código donde tenemos un enlace a la función miembro, tengo algunas preguntas.
struct Foo {
void print_sum(int n1, int n2)
{
std::cout << n1+n2 << ''/n'';
}
int data = 10;
};
Foo foo;
auto f = std::bind(&Foo::print_sum, &foo, 95, _1);
f(5);
-
¿Por qué el primer argumento es una referencia? Me gustaría obtener una explicación teórica.
-
El segundo argumento es una referencia al objeto y es para mí la parte más complicada de entender. Creo que es porque
std::bind
necesita un contexto, ¿estoy en lo cierto? ¿Siempre es así? ¿std::bind
algún tipo de implementación para requerir una referencia cuando el primer argumento es una función miembro?
De std :: bind docs :
bind( F&& f, Args&&... args );
donde f es un
Callable
, en su caso es un puntero a la función miembro.
Este tipo de punteros tiene una sintaxis especial en comparación con los punteros de las funciones habituales:
typedef void (Foo::*FooMemberPtr)(int, int);
// obtain the pointer to a member function
FooMemberPtr a = &Foo::print_sum; //instead of just a = my_divide
// use it
(foo.*a)(1, 2) //instead of a(1, 2)
std::bind
(y
std::invoke
en general
) cubre todos estos casos de manera uniforme.
Si
f
es un puntero a miembro de
Foo
, se espera que el primer
Arg
proporcionado para enlazar sea una instancia de
Foo
(
bind(&Foo::print_sum, foo, ...)
también funciona, pero se copia
foo
) o un
puntero
a
Foo
, como en el ejemplo que tenías.
Aquí hay más información sobre los punteros para los miembros , y 1 y 2 brindan información completa sobre lo que Bind espera y cómo invoca la función almacenada.
También puede usar lambdas en su lugar
std::bind
, que podría ser más claro:
auto f = [&](int n) { return foo.print_sum(95, n); }