c++ c++11 std stdbind

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); }