stdinvoke详解
std::invoke详解
基础介绍
c++17版本引入了std::invoke特性,这是一个通用的调用包装器,可以统一调用:
- 普通函数
- 成员函数
- 函数对象
- Lambda表达式
- 指向成员的指针 它的主要作用是提供一个统一的方式来调用各种可调用对象 。 std::invoke依赖的头文件:**#include **
基本用法
下面将详细介绍基本用法,即对上节中提到的对象(普通函数、成员函数、函数对象、Lambda表达式等)的调用。 #include #include using namespace std; //普通函数 void basic_function(int x) { cout «" 普通函数:"< #include class Example { public: void method(int x) { std::cout « “Method called: " « x « “\n”; } int value = 42; }; void normal_function(int x) { std::cout « “Function called: " « x « “\n”; } void unified_call_syntax() { Example obj; // 不使用 std::invoke 时的不同调用语法 normal_function(1); // 普通函数调用 obj.method(2); // 成员函数调用 int val = obj.value; // 成员变量访问 // 使用 std::invoke 的统一语法 std::invoke(normal_function, 1); // 普通函数 std::invoke(&Example::method, obj, 2); // 成员函数 std::invoke(&Example::value, obj); // 成员变量 } 从上面的例子可以看到,如果不使用std::invoke,那么不同的函数对象的对象方法和形式各不相同;但是引入std::invoke后,可以很明显的看到针对不同的函数对象实现了相同的调用形式。
泛型编程的支持
前面的例子是针对不同的函数对象不同调用,但是提到泛型编程,就会涉及不同的函数对象,不同的参数数量和类型。那如何设计一个函数可以实现不同的函数对象类型,不同参数数量和参数类型的调用呢?首先肯定是需要依靠模板实现的。请看下面的例子: #include #include #include //函数模板 template decltype(auto) modern_call(F&& f, Args&&… args) { return std::invoke( std::forward(f), std::forward(args)); } //普通函数 void normal_function(int x) { std::cout « “Function called: " « x « “\n”; } //示范类 class Calculator { public: int add(int a, int b) { return a + b; } double factor = 1.5; }; void example() { Calculator calc; // 可以统一处理各种可调用对象 modern_call(normal_function, 1); // 普通函数 modern_call(&Calculator::add, calc, 2, 3); // 成员函数 modern_call(&Calculator::factor, calc); // 成员变量 modern_call([](int x) { return x * 2; }, 5); // lambda表达式 } 通过上面的例子可以看到,通过modern_call的封装,实现了不同类型的函数对象的统一调用。可以这样说,若要实现对不同函数对象的统一调用的支持,必须要依靠模板的方式实现对std::invoke的封装。那这种泛型编程的应用场景有哪些呢?
- 回调系统
- 事件系统
- 命令模式 具体请看下面的例子: #include #include #include #include // 1. 事件系统 class EventSystem { public: template void trigger(F&& handler, Args&&… args) { std::invoke( std::forward(handler), std::forward(args)… ); } }; // 2. 命令模式 class Command { std::function action; public: template Command(F&& f, Args&&… args) { action = { std::invoke(f, args…); }; } void execute() { action(); } }; // 3. 回调系统 class CallbackSystem { public: template void registerCallback(Callback&& cb, Args&&… args) { callbacks.emplace_back( { std::invoke(cb, args…); }); } void executeAll() { for (auto& callback : callbacks) { callback(); } } private: std::vector> callbacks; }; 通过上面的例子可以清楚的看到各种场景下的使用方法,但是相同点都是在函数内部都是通过定义函数模板(泛型编程)实现的。
支持智能指针和引用包装器
#include #include #include class Service { public: int process(int x) { return x * 2; } }; void smart_pointer_example() { // 智能指针支持 auto ptr = std::make_shared(); auto unique = std::make_unique(); // std::invoke 可以直接使用智能指针 int result1 = std::invoke(&Service::process, ptr, 10); int result2 = std::invoke(&Service::process, unique, 20); // 引用包装器支持 Service service; auto ref = std::ref(service); int result3 = std::invoke(&Service::process, ref, 30); }
总结
我们需要有两个认识:
- std::invoke可以实现对函数对象的调用,达到与直接调用函数相同的效果
- 如果要实现类似回调系统、事件系统类似的功能,需要集合模板来实现