C++C++17万能调用神技 std::invoke
苏丙榅1. 什么是 std::invoke
在 C++17 之前,如果想调用一个函数,直接写 func(args);如果想调用一个对象的成员函数,需要写 obj.func(args) 或者 ptr->func(args)。
但在写一些通用的代码(比如库代码、模板)时,编译器并不知道我们传进来的是一个普通函数、一个成员函数,还是一个像函数一样的对象(比如 lambda)。这时候,写法就非常麻烦了。
1 2 3 4 5 6 7 8 9
| func(args...);
obj.func_ptr(args...); obj->func_ptr(args...);
func_obj(args...);
|
std::invoke 是 C++17 引入的一个函数模板,用于统一调用各种可调用对象。简单来说,std::invoke 是一个万能调用器,它提供了一种标准化的方式来调用函数、成员函数、函数对象等,无论它们是什么类型,这样可以让代码更加一致和可读。
1 2 3 4 5 6 7
| #include <functional>
template< class F, class... Args > std::invoke_result_t<F, Args...> std::invoke(F&& f, Args&&... args);
std::invoke(可调用对象, 参数1, 参数2, ...);
|
该函数返回的就是调用结果,就像我们直接调用那个函数(可调用对象)一样。
2. 应用场景
2.1 调用普通函数和 Lambda
调用普通函数和Lambda这是最简单的用法,虽然看起来和直接调用差不多,但它是通用的基础。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #include <iostream> #include <functional> #include <string>
void print_hello(const std::string& name) { std::cout << "你好, " << name << "!" << std::endl; }
struct Printer { void operator()(int x) { std::cout << "数字是: " << x << std::endl; } };
int main() { std::invoke(print_hello, "小明");
auto lambda = [](double d) { std::cout << "Pi 约等于: " << d << std::endl; }; std::invoke(lambda, 3.14159);
Printer p; std::invoke(p, 100);
return 0; }
|
2.2 调用类的成员函数
调用类的成员函数这是 std::invoke 最闪光的地方。以前处理成员函数非常麻烦,因为需要把对象和函数指针拼起来。调用类的成员函数时,语法主要取决于对象是以引用、指针还是智能指针的形式传递。
1
| std::invoke(&ClassName::MethodName, instance, args...);
|
- 参数1:成员函数的指针必须使用取址符
&。
- 参数2:类的实例对象或者对象引用、对象指针或者智能指针。
- 参数3…:传递给成员函数的参数。
假设有如下类:
1 2 3 4 5 6 7 8
| class Widget { public: void execute(int x) { std::cout << "Executing with " << x << std::endl; } };
|
使用对象实例或引用:最常用的方式。
1 2 3 4 5 6 7
| Widget w;
std::invoke(&Widget::execute, w, 42);
Widget w1;
std::invoke(&Widget::execute, std::ref(w1), 42);
|
使用对象指针:传递对象的指针(this 指针)。
1 2 3
| Widget *ptr = new Widget;
std::invoke(&Widget::execute, ptr, 42);
|
使用智能指针:std::invoke 能够自动解引用 std::shared_ptr 和 std::unique_ptr
1 2 3 4 5
| auto ptr1 = std::make_unique<Widget>(); auto ptr2 = std::make_shared<Widget>();
std::invoke(&Widget::execute, ptr1, 42); std::invoke(&Widget::execute, ptr2, 42);
|
当我们需要对对象列表进行排序时,经常需要根据某个字段排序。利用 std::invoke,可以写一个通用的比较器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| #include <iostream> #include <vector> #include <string> #include <algorithm>
class Player { private: std::string name; int score; public: Player(std::string n, int s) : name(n), score(s) {}
int getScore() const { return score; } std::string getName() const { return name; }
int getEffectiveScore() const { return score; } };
template <auto Member> struct CompareBy { template <typename T> bool operator()(const T& a, const T& b) const { return std::invoke(Member, a) < std::invoke(Member, b); } };
int main() { std::vector<Player> players = { {"Bob", 100}, {"Alice", 200}, {"Charlie", 150} };
std::cout << "排序前:" << std::endl; for (const auto& p : players) { std::cout << p.getName() << ": " << p.getScore() << std::endl; } std::cout << "-------------------" << std::endl;
std::sort(players.begin(), players.end(), CompareBy<&Player::getEffectiveScore>{});
std::cout << "排序后 (升序):" << std::endl; for (const auto& p : players) { std::cout << p.getName() << ": " << p.getScore() << std::endl; } return 0; }
|
2.3 调用重载的成员函数
如果成员函数被重载了(即有几个同名函数,参数不同),直接写 &ClassName::Func 会导致编译器无法确定是哪一个。必须显式转换为正确的函数指针类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <iostream> #include <functional>
class Widget { public: void process(int x) { std::cout << "Int: " << x << std::endl; } void process(double x) { std::cout << "Double: " << x << std::endl; } };
int main() { Widget w; using FuncType = void (Widget::*)(int); std::invoke(static_cast<FuncType>(&Widget::process), w, 10); return 0; }
|
2.4 访问类的数据成员
std::invoke 的核心设计理念是:统一调用可调用对象。而在 C++ 中,成员变量(非静态数据成员)也被视为一种可调用对象。当你使用 std::invoke 配合成员变量指针时,它实际上执行的是成员访问操作,而不是函数调用。对于成员变量,std::invoke 的行为等效于:
- 对象或指针 :
std::invoke(&Class::member, obj) 等价于 obj.member、obj->member
- 对象引用:
std::invoke(&Class::member, std::ref(obj)) 等价于 obj.member
假设有一个通用的打印函数,它既可以处理成员函数(获取结果),也可以直接处理成员变量(直接读取值),完全不需要重载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #include <iostream> #include <functional> #include <string>
struct User { std::string name; int age; std::string getRole() const { return "Admin"; } };
template <auto Member, typename Obj> void printMember(const Obj& obj) { std::cout << "Value: " << std::invoke(Member, obj) << std::endl; }
int main() { User u{"Alice", 30};
std::cout << "Direct Field: "; printMember<&User::name>(u);
std::cout << "Direct Field: "; printMember<&User::age>(u);
std::cout << "Method Call: "; printMember<&User::getRole>(u); return 0; }
|