Как std::bind работает с функциями-членами
Я работаю с std::bind
но я до сих пор не понимаю, как это работает, когда мы используем его с функциями класса-члена.
Если у нас есть следующая функция:
double my_divide (double x, double y) {return x/y;}
Я прекрасно понимаю следующие строки кода:
auto fn_half = std::bind (my_divide,_1,2); // returns x/2
std::cout << fn_half(10) << '\n'; // 5
Но теперь, со следующим кодом, в котором у нас есть привязка к функции-члену, у меня есть несколько вопросов.
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);
Почему первый аргумент является ссылкой? Я хотел бы получить теоретическое объяснение.
Второй аргумент - это ссылка на объект, и для меня это самая сложная часть для понимания. Я думаю это потому что
std::bind
нужен контекст, я прав? Это всегда так? имеетstd::bind
какая-то реализация, требующая ссылки, когда первый аргумент является функцией-членом?
2 ответа
Когда вы говорите "первый аргумент является ссылкой", вы наверняка хотели сказать "первый аргумент является указателем": &
Оператор берет адрес объекта, получая указатель.
Прежде чем ответить на этот вопрос, давайте вкратце сделаем шаг назад и рассмотрим ваше первое использование std::bind()
когда вы используете
std::bind(my_divide, 2, 2)
Вы предоставляете функцию. Когда функция передается где-либо, она превращается в указатель. Вышеупомянутое выражение эквивалентно этому, явно принимая адрес
std::bind(&my_divide, 2, 2)
Первый аргумент std::bind()
является объектом, определяющим, как вызвать функцию. В приведенном выше случае это указатель на функцию с типом double(*)(double, double)
, Подойдет любой другой вызываемый объект с подходящим оператором вызова функции.
Поскольку функции-члены довольно распространены, std::bind()
обеспечивает поддержку для работы с указателем на функции-члены. Когда вы используете &print_sum
вы просто получаете указатель на функцию-член, т.е. на объект типа void (Foo::*)(int, int)
, В то время как имена функций неявно распадаются на указатели на функции, т.е. &
может быть опущен, то же самое не верно для функций-членов (или членов данных, в этом отношении): чтобы получить указатель на функцию-член, необходимо использовать &
,
Обратите внимание, что указатель на член является специфическим для class
но его можно использовать с любым объектом этого класса. То есть он не зависит от какого-либо конкретного объекта. C++ не имеет прямого способа получить функцию-член, напрямую связанную с объектом (я думаю, что в C# вы можете получить функции, напрямую связанные с объектом, используя объект с примененным именем члена; однако с тех пор прошло более 10 лет Я в последний раз запрограммировал немного C#).
Внутренне std::bind()
обнаруживает, что передан указатель на функцию-член, и, скорее всего, превращает его в вызываемые объекты, например, с помощью std::mem_fn()
с его первым аргументом. Поскольку неstatic
Функция-член нуждается в объекте, первый аргумент вызываемого объекта разрешения - это либо ссылка, либо [умный] указатель на объект соответствующего класса.
Чтобы использовать указатель на функцию-член, необходим объект. При использовании указателя на член с std::bind()
второй аргумент std::bind()
соответственно необходимо указать, когда объект приходит. В вашем примере
std::bind(&Foo::print_sum, &foo, 95, _1)
полученный вызываемый объект использует &foo
указатель на foo
(типа Foo*
) как объект. std::bind()
достаточно умен, чтобы использовать все, что выглядит как указатель, все, что можно преобразовать в ссылку соответствующего типа (например, std::reference_wrapper<Foo>
) или [копия] объекта как объекта, когда первый аргумент является указателем на член.
Я подозреваю, вы никогда не видели указатель на член - иначе это было бы совершенно ясно. Вот простой пример:
#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);
}
Функция apply()
просто получает два указателя на Foo
объекты и указатель на функцию-член. Он вызывает функцию-член, указанную для каждого из объектов. Это смешно ->*
Оператор применяет указатель на член к указателю на объект. Также есть .*
оператор, который применяет указатель на член к объекту (или, поскольку они ведут себя так же, как объекты, ссылку на объект). Поскольку указатель на функцию-член нуждается в объекте, необходимо использовать этот оператор, который запрашивает объект. Внутренне std::bind()
устраивает то же самое.
когда apply()
вызывается с двумя указателями и &Foo::f
он ведет себя точно так же, как если бы член f()
будет вызван на соответствующие объекты. Аналогично при звонке apply()
с двумя указателями и &Foo::g
он ведет себя точно так же, как если бы член g()
будет вызываться для соответствующих объектов (семантическое поведение такое же, но компилятору, скорее всего, будет гораздо сложнее вставлять функции и обычно он этого не делает, когда задействованы указатели на члены).
Из std:: bind docs:
bind( F&& f, Args&&... args );
где f является Callable
в вашем случае это указатель на функцию-член. Этот тип указателей имеет некоторый специальный синтаксис по сравнению с указателями на обычные функции:
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
(а также std::invoke
в общем) охватывает все эти случаи единообразно. Если f
указатель на член Foo
тогда первый Arg
при условии связывания, как ожидается, будет экземпляром Foo
(bind(&Foo::print_sum, foo, ...)
тоже работает, но foo
копируется) или указатель на Foo
как в примере с вами.
Вот еще немного прочтения об указателях на члены, а 1 и 2 дают полную информацию о том, что ожидает связывание и как оно вызывает хранимую функцию.
Вы также можете использовать лямбды вместо std::bind
что может быть более понятно:
auto f = [&](int n) { return foo.print_sum(95, n); }