При использовании std::function
для вызова нестатической функции-члена мы можем передать либо указатель объекта, либо ссылку на объект в качестве первого параметра:
struct Foo {
void bar() const { std::cout << "Foo::bar called "<< std::endl; }
};
int main() {
Foo foo;
// version1: pass the object pointer
std::function<void(Foo*)> call_bar_by_pointer = &Foo::bar;
call_bar_by_pointer(&foo);
// or, version2: pass the object reference
std::function<void(Foo&)> call_bar_by_reference = &Foo::bar;
call_bar_by_reference(foo);
return 0;
}
В моем предыдущем понимании нестатическая функция-член по существу неявно имеет указатель объекта в качестве первого аргумента. Поэтому, когда дело доходит до std::function
для вызова нестатической функции-члена, я также ожидаю, что первый параметр в списке параметров будет указателем на тип объекта (т. е. version1).
Может ли кто-нибудь объяснить, почему здесь также поддерживается ссылочный тип (т. е. версия 2) и как он работает?
std::function
можно использовать для любого вызываемого.
Он использует определение INVOKE, а именно:
INVOKE(f, t1, t2, ..., tN) определяется следующим образом:
- если f является указателем на функцию-член класса T:
- (1) если
std::is_base_of<T, std::remove_reference_t<decltype(t1)>>::value
естьtrue
, тоINVOKE(f, t1, t2, ..., tN)
эквивалентно(t1.*f)(t2, ..., tN)
- (2) в противном случае [...] (
std::reference_wrapper
специализация)- (3) иначе, если t1 не удовлетворяет предыдущим пунктам, то
INVOKE(f, t1, t2, ..., tN)
эквивалентно((*t1).*f)(t2, ..., tN)
.
Точка (1) работает, когда t1
является простым значением или ссылкой, а точка (3) работает, когда t1
является указателем (также может быть интеллектуальным указателем).
Именно так реализован std::function
(и все, что использует INVOKE). Без него вы не можете использовать тот же синтаксис, вы должны использовать оператор указателя на член:
using BarType = void (Foo::*)() const;
BarType normal_function_pointer = &Foo::bar;
(foo.*normal_function_pointer)();
Обратите внимание, что std::function
работает даже с std::reference_wrapper
(пункт (2)), и он может работать с другими вещами, такими как двойные указатели, но это просто не реализовано (и, вероятно, бесполезно).
Это не языковая функция, т.е. ни один из
void(*call_bar_by_pointer)(Foo*) = &Foo::bar; void(*call_bar_by_reference)(Foo&) = &Foo::bar;
не скомпилируется. Реализация пользовательского класса может вызывать все, что выглядит разумным и полезным, например. вы можете написать реализацию шаблона функции, которая будет принимать и вызывать Foo**.