基础介绍
c++17版本引入了std::invoke特性,这是一个通用的调用包装器,可以统一调用:
- 普通函数
- 成员函数
- 函数对象
- Lambda表达式
- 指向成员的指针
它的主要作用是提供一个统一的方式来调用各种可调用对象。
std::invoke依赖的头文件:#include <functional>
基本用法
下面将详细介绍基本用法,即对上节中提到的对象(普通函数、成员函数、函数对象、Lambda表达式等)的调用。
#include <functional>
#include <iostream>
using namespace std;
//普通函数
void basic_function(int x)
{
cout <<" 普通函数:"<<x<<endl;
}
//具有返回值的普通函数
int add(int a, int b)
{
return a + b;
}
//成员函数
class MyClass{
public:
void member_function(int x)
{
cout <<"成员函数:"<<x<<endl;
}
int value = 33;
}
//函数对象
class Functor
{
public:
void operator()(int x)
{
cout <<"仿函数对象"<<x<<endl;
}
}
//示例函数
void basic_usage()
{
//调用普通函数
std::invoke(basic_function, 5);
//调用具有返回值的普通函数
int value = std::invoke(add, 4, 5);
//调用成员函数
MyClass obj;
std::invoke(&MyClass::member_function, obj, 5);
//调用仿函数对象
Functor funtor;
std::invoke(funtor, 5);
//调用lambda表达式
std::invoke([](int x){
cout <<"lambda表达式:"<<x<<endl;
}, 5);
//访问成员变量,这个成员变量必须时public的
std::invoke(&Myclass::value, obj);
}
特性总结
通过上面示例可以得到以下结论:
- std::invoke表示函数调用:只要调用std::invoke,且执行了这个语句,那么就相当于调用了传入的函数对象
- std::invoke的含义传入一个函数对象及这个函数对象的参数,然后通过std::invoke完成这个函数的调用
思考:为什么引入std::invoke?
统一的调用语法
函数对象有多种,比如普通函数,成员函数、仿函数对象,lambda表达式等不同的形式,不同的函数对象的调用方法都不相同,请看下面的例子:
#include <functional>
#include <iostream>
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 <functional>
#include <iostream>
#include <type_traits>
//函数模板
template<typename F, typename... Args>
decltype(auto) modern_call(F&& f, Args&&... args)
{
return std::invoke(
std::forward<F>(f),
std::forward<Args>(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 <functional>
#include <iostream>
#include <vector>
#include <string>
// 1. 事件系统
class EventSystem {
public:
template<typename F, typename... Args>
void trigger(F&& handler, Args&&... args) {
std::invoke(
std::forward<F>(handler),
std::forward<Args>(args)...
);
}
};
// 2. 命令模式
class Command {
std::function<void()> action;
public:
template<typename F, typename... Args>
Command(F&& f, Args&&... args) {
action = [=]() {
std::invoke(f, args...);
};
}
void execute() { action(); }
};
// 3. 回调系统
class CallbackSystem {
public:
template<typename Callback, typename... Args>
void registerCallback(Callback&& cb, Args&&... args) {
callbacks.emplace_back([=]() {
std::invoke(cb, args...);
});
}
void executeAll() {
for (auto& callback : callbacks) {
callback();
}
}
private:
std::vector<std::function<void()>> callbacks;
};
通过上面的例子可以清楚的看到各种场景下的使用方法,但是相同点都是在函数内部都是通过定义函数模板(泛型编程)实现的。
支持智能指针和引用包装器
#include <functional>
#include <memory>
#include <iostream>
class Service {
public:
int process(int x) { return x * 2; }
};
void smart_pointer_example() {
// 智能指针支持
auto ptr = std::make_shared<Service>();
auto unique = std::make_unique<Service>();
// 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可以实现对函数对象的调用,达到与直接调用函数相同的效果
- 如果要实现类似回调系统、事件系统类似的功能,需要集合模板来实现