概述
C++17引入的一个强大的函数调用工具,它提供了一种统一的方式来调用各种可调用对象- 作用目标
- 普通函数
- 成员函数
- 成员变量
- 函数对象(包括
lambda) - 任何重载了
operator()的对象
- 为什么需要
std::invoke- 在
C++17之前,调用不同类型的可调用对象需要不同的语法 std::invoke提供了统一的调用方式,这在泛型编程中非常重要
- 在
使用场景
普通函数
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <functional> #include <iostream> int add(int a, int b) { return a + b; } int main() { // 直接调用 int result = std::invoke(add, 10, 20); std::cout << result << std::endl; // 输出: 30 // 通过函数指针 int (*func_ptr)(int, int) = add; result = std::invoke(func_ptr, 5, 15); std::cout << result << std::endl; // 输出: 20 } |
成员函数
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 |
#include <functional> #include <iostream> #include <memory> class Calculator { public: int add(int a, int b) const { return a + b; } int multiply(int a, int b) { return a * b; } }; int main() { Calculator calc; // 1. 通过对象调用 int result1 = std::invoke(&Calculator::add, calc, 10, 20); std::cout << result1 << std::endl; // 输出: 30 // 2. 通过指针调用 Calculator* ptr = &calc; int result2 = std::invoke(&Calculator::multiply, ptr, 3, 4); std::cout << result2 << std::endl; // 输出: 12 // 3. 通过引用调用 Calculator& ref = calc; int result3 = std::invoke(&Calculator::add, ref, 5, 10); std::cout << result3 << std::endl; // 输出: 15 // 4. 通过智能指针调用 auto smart_ptr = std::make_shared<Calculator>(); int result4 = std::invoke(&Calculator::multiply, smart_ptr, 6, 7); std::cout << result4 << std::endl; // 输出: 42 } |
访问成员变量
|
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 |
#include <functional> #include <iostream> class Person { public: std::string name; int age; Person(std::string n, int a) : name(n), age(a) {} }; int main() { Person person("Alice", 30); // 通过对象访问 std::string& name = std::invoke(&Person::name, person); std::cout << name << std::endl; // 输出: Alice // 通过指针访问 Person* ptr = &person; int& age = std::invoke(&Person::age, ptr); std::cout << age << std::endl; // 输出: 30 // 修改成员变量 std::invoke(&Person::age, person) = 31; std::cout << person.age << std::endl; // 输出: 31 } |
调用函数对象和 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 |
#include <functional> #include <iostream> struct Functor { int operator()(int a, int b) const { return a * b; } }; int main() { // 函数对象 Functor functor; int result1 = std::invoke(functor, 5, 6); std::cout << result1 << std::endl; // 输出: 30 // Lambda auto lambda = [](int a, int b) { return a + b; }; int result2 = std::invoke(lambda, 10, 20); std::cout << result2 << std::endl; // 输出: 30 // 带捕获的 Lambda int multiplier = 3; auto capturing_lambda = [multiplier](int x) { return x * multiplier; }; int result3 = std::invoke(capturing_lambda, 7); std::cout << result3 << std::endl; // 输出: 21 } |
高级应用
泛型包装器
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 |
#include <functional> #include <iostream> #include <chrono> // 通用的性能测试包装器 template<typename Callable, typename... Args> auto measure_time(Callable&& func, Args&&... args) { auto start = std::chrono::high_resolution_clock::now(); // 使用 std::invoke 统一调用各种可调用对象 auto result = std::invoke(std::forward<Callable>(func), std::forward<Args>(args)...); auto end = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start); std::cout << "执行时间: " << duration.count() << " 微秒" << std::endl; return result; } class MyClass { public: int heavy_computation(int n) { int sum = 0; for (int i = 0; i < n; ++i) { sum += i; } return sum; } }; int main() { // 测试普通函数 auto lambda = [](int n) { int sum = 0; for (int i = 0; i < n; ++i) sum += i; return sum; }; auto result1 = measure_time(lambda, 1000000); // 测试成员函数 MyClass obj; auto result2 = measure_time(&MyClass::heavy_computation, &obj, 1000000); } |
实现类似 std::apply 的功能
|
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 |
#include <functional> #include <tuple> #include <iostream> // 简化版的 apply 实现 template<typename F, typename Tuple, std::size_t... I> decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>) { return std::invoke(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...); } template<typename F, typename Tuple> decltype(auto) my_apply(F&& f, Tuple&& t) { return apply_impl( std::forward<F>(f), std::forward<Tuple>(t), std::make_index_sequence<std::tuple_size_v<std::decay_t<Tuple>>>{} ); } int add_three(int a, int b, int c) { return a + b + c; } int main() { auto args = std::make_tuple(1, 2, 3); int result = my_apply(add_three, args); std::cout << result << std::endl; // 输出: 6 } |
回调系统
|
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 |
#include <functional> #include <iostream> #include <vector> #include <string> class EventSystem { private: struct Callback { void* object; void* function; template<typename Obj, typename Func> Callback(Obj* obj, Func func) : object(obj), function(reinterpret_cast<void*>(func)) {} }; std::vector<std::function<void(const std::string&)>> callbacks; public: // 添加任意类型的回调 template<typename Callable> void add_callback(Callable&& callback) { callbacks.push_back(std::forward<Callable>(callback)); } // 触发事件 void trigger(const std::string& message) { for (auto& callback : callbacks) { std::invoke(callback, message); } } }; class Logger { public: void log(const std::string& msg) { std::cout << "[LOG] " << msg << std::endl; } }; int main() { EventSystem events; Logger logger; // 添加成员函数回调 events.add_callback([&logger](const std::string& msg) { std::invoke(&Logger::log, &logger, msg); }); // 添加 Lambda 回调 events.add_callback([](const std::string& msg) { std::cout << "[LAMBDA] " << msg << std::endl; }); // 触发事件 events.trigger("事件发生了!"); } |
相关工具类型
std::invoke_result
- 用于获取调用结果的类型
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <functional> #include <type_traits> #include <iostream> int add(int a, int b) { return a + b; } class MyClass { public: double compute(int x) { return x * 1.5; } }; int main() { // 获取普通函数的返回类型 using ResultType1 = std::invoke_result_t<decltype(add), int, int>; static_assert(std::is_same_v<ResultType1, int>); // 获取成员函数的返回类型 using ResultType2 = std::invoke_result_t decltype(&MyClass::compute), MyClass*, int>; static_assert(std::is_same_v<ResultType2, double>); std::cout << "类型检查通过!" << std::endl; } |
std::is_invocable
- 检查是否可以用给定参数调用
|
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 |
#include <functional> #include <type_traits> #include <iostream> void func(int, double) {} class MyClass { public: void method(int) {} }; int main() { // 检查是否可调用 static_assert(std::is_invocable_v<decltype(func), int, double>); static_assert(!std::is_invocable_v<decltype(func), int, int, int>); // 检查成员函数 static_assert(std::is_invocable_v decltype(&MyClass::method), MyClass*, int>); // 检查返回类型 static_assert(std::is_invocable_r_v<void, decltype(func), int, double>); std::cout << "所有检查通过!" << std::endl; } |
实现原理
源码
|
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 |
_EXPORT_STD template <class _Callable> constexpr auto invoke(_Callable&& _Obj) noexcept(noexcept(static_cast<_Callable&&>(_Obj)())) -> decltype(static_cast<_Callable&&>(_Obj)()) { return static_cast<_Callable&&>(_Obj)(); } _EXPORT_STD template <class _Callable, class _Ty1, class... _Types2> constexpr auto invoke(_Callable&& _Obj, _Ty1&& _Arg1, _Types2&&... _Args2) noexcept(noexcept(_Invoker1<_Callable, _Ty1>::_Call( static_cast<_Callable&&>(_Obj), static_cast<_Ty1&&>(_Arg1), static_cast<_Types2&&>(_Args2)...))) // -> decltype(_Invoker1<_Callable, _Ty1>::_Call( static_cast<_Callable&&>(_Obj), static_cast<_Ty1&&>(_Arg1), static_cast<_Types2&&>(_Args2)...)) { if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Functor) { return static_cast<_Callable&&>(_Obj)(static_cast<_Ty1&&>(_Arg1), static_cast<_Types2&&>(_Args2)...); } else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmf_object) { return (static_cast<_Ty1&&>(_Arg1).*_Obj)(static_cast<_Types2&&>(_Args2)...); } else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmf_refwrap) { return (_Arg1.get().*_Obj)(static_cast<_Types2&&>(_Args2)...); } else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmf_pointer) { return ((*static_cast<_Ty1&&>(_Arg1)).*_Obj)(static_cast<_Types2&&>(_Args2)...); } else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmd_object) { return static_cast<_Ty1&&>(_Arg1).*_Obj; } else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmd_refwrap) { #if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-1956799 return _Arg1.get().*_Obj; #else // ^^^ no workaround / workaround vvv auto& _Ref = _Arg1.get(); return _Ref.*_Obj; #endif // ^^^ workaround ^^^ } else { _STL_INTERNAL_STATIC_ASSERT(_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmd_pointer); return (*static_cast<_Ty1&&>(_Arg1)).*_Obj; } } |
无参数版本
- 尾置返回类型
decltype推导调用_Obj()的返回类型static_cast<_Callable&&>是完美转发的手动实现
|
1 |
-> decltype(static_cast<_Callable&&>(_Obj)()) |
noexcept规范- 外层
noexcept:声明函数的异常规范 - 内层
noexcept:检查表达式是否不抛异常 - 这是
noexcept传播:如果调用不抛异常,invoke也不抛
- 外层
|
1 2 3 4 5 6 7 8 9 10 |
struct NoThrow { void operator()() noexcept { } }; struct MayThrow { void operator()() { } // 可能抛异常 }; // invoke(NoThrow{}) 的 noexcept 为 true // invoke(MayThrow{}) 的 noexcept 为 false |
static_cast<_Callable&&>- 这不是简单的类型转换
- 当
_Callable是T&(左值引用)时,_Callable&&变成T& &&,折叠为T& - 当
_Callable是T(非引用)时,_Callable&&就是T&&(右值引用) - 所以它是完美转发的核心机制
|
1 |
static_cast<_Callable&&>(_Obj) |
带参数版本
- 函数签名
_Callable&&:被调用的对象(函数、成员函数指针等)_Ty1&&:第一个参数(特殊处理,因为可能是对象)_Types2&&...:剩余参数包
|
1 2 |
template <class _Callable, class _Ty1, class... _Types2> constexpr auto invoke(_Callable&& _Obj, _Ty1&& _Arg1, _Types2&&... _Args2) |
- 为什么要单独提取第一个参数
- 因为调用成员函数时,第一个参数是对象
|
1 2 |
obj.func(args...) // obj 是第一个参数 (obj.*pmf)(args...) // obj 是第一个参数 |
- 返回类型和
noexcept- 使用
_Invoker1辅助类来决定返回类型和异常规范 _Invoker1会分析_Callable和_Ty1的类型,确定调用策略
- 使用
|
1 2 3 4 |
noexcept(noexcept(_Invoker1<_Callable, _Ty1>::_Call( static_cast<_Callable&&>(_Obj), static_cast<_Ty1&&>(_Arg1), static_cast<_Types2&&>(_Args2)...))) -> decltype(_Invoker1<_Callable, _Ty1>::_Call( static_cast<_Callable&&>(_Obj), static_cast<_Ty1&&>(_Arg1), static_cast<_Types2&&>(_Args2)...)) |
_Functor(普通函数对象)- 调用普通函数、
lambda、函数对象
- 调用普通函数、
|
1 2 3 4 5 6 |
if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Functor) { return static_cast<_Callable&&>(_Obj)( static_cast<_Ty1&&>(_Arg1), static_cast<_Types2&&>(_Args2)... ); } |
_Pmf_object(成员函数指针 + 对象).*是成员指针运算符(obj.*pmf)(args...)通过对象调用成员函数
|
1 2 3 |
else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmf_object) { return (static_cast<_Ty1&&>(_Arg1).*_Obj)(static_cast<_Types2&&>(_Args2)...); } |
|
1 2 3 4 5 6 7 |
// 示例 struct MyClass { int add(int a, int b) { return a + b; } }; MyClass obj; invoke(&MyClass::add, obj, 10, 20); // (obj.*&MyClass::add)(10, 20) |
|
1 2 3 4 5 6 7 8 9 10 11 |
// 完美转发的重要性 struct MyClass { void modify() & { /* 只能用左值调用 */ } void move_modify() && { /* 只能用右值调用 */ } }; MyClass obj; invoke(&MyClass::modify, obj); // OK,obj 是左值 invoke(&MyClass::move_modify, MyClass{}); // OK,临时对象是右值 // static_cast<_Ty1&&> 保持了值类别! |
_Pmf_refwrap(成员函数指针 +reference_wrapper)- 处理
std::ref和std::cref - 为什么需要特殊处理
reference_wrapper不是对象本身,需要调用.get()获取真正的引用
- 处理
|
1 2 3 |
else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmf_refwrap) { return (_Arg1.get().*_Obj)(static_cast<_Types2&&>(_Args2)...); } |
|
1 2 3 4 5 6 7 8 9 10 |
// 示例 struct MyClass { int value = 42; int get_value() const { return value; } }; MyClass obj; auto ref = std::ref(obj); // reference_wrapper<MyClass> invoke(&MyClass::get_value, ref); // 需要先 .get() 获取引用 |
_Pmf_pointer(成员函数指针 + 指针)- 语法:
((*ptr).*pmf)(args...) - 为什么要先解引用
因为.*运算符要求左操作数是对象,不能是指针
- 语法:
|
1 2 3 |
else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmf_pointer) { return ((*static_cast<_Ty1&&>(_Arg1)).*_Obj)(static_cast<_Types2&&>(_Args2)...); } |
|
1 2 3 4 5 6 7 8 9 10 11 12 |
// 示例 struct MyClass { int add(int a, int b) { return a + b; } }; MyClass obj; MyClass* ptr = &obj; invoke(&MyClass::add, ptr, 10, 20); // ((*ptr).*&MyClass::add)(10, 20) // 也支持智能指针! auto smart_ptr = std::make_unique<MyClass>(); invoke(&MyClass::add, smart_ptr, 5, 15); // (*smart_ptr).*... 工作正常 |
_Pmd_object(成员变量指针 + 对象)- 访问成员变量
|
1 2 3 |
else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmd_object) { return static_cast<_Ty1&&>(_Arg1).*_Obj; } |
_Pmd_refwrap(成员变量指针 +reference_wrapper)MSVC在某些情况下无法正确处理reference_wrapper.get().*member,需要先存储引用
|
1 2 3 4 5 6 7 8 |
else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmd_refwrap) { #if defined(__clang__) || defined(__EDG__) return _Arg1.get().*_Obj; #else auto& _Ref = _Arg1.get(); return _Ref.*_Obj; #endif } |
_Pmd_pointer(成员变量指针 + 指针)- 通过指针访问成员变量
|
1 2 3 4 |
else { _STL_INTERNAL_STATIC_ASSERT(_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmd_pointer); return (*static_cast<_Ty1&&>(_Arg1)).*_Obj; } |
_Invoker1
- todo
其他
_EXPORT_STD
MSVC的内部宏,用于控制符号导出- 在模块化编程(
C++20 modules)中标记需要导出的符号
decltype和auto
auto:会执行表达式,然后根据结果推导类型- 但类型的确认是在编译期,根据的是表达式的签名
- 表达式的求值是在运行期,两者不矛盾
decltype:不会执行表达式,只在编译期分析类型- 不存在运行期的事情
| 特性 | auto |
decltype |
| 推断方式 | 类似模板参数推断,会退化(decay) |
精确保留表达式的类型 |
| 引用处理 | 忽略顶层引用 | 保留引用 |
const 处理 |
忽略顶层 const |
保留 const |
| 用途 | 变量声明 | 类型查询 |
- 既然
auto和decltyp推断的类型都是编译期确定,那么为啥auto推断的类型会有decay现象C++11设计auto时,为了一致性,让它的推导规则与模板参数推导完全相同
- 那么为什么模板参数会退化
- 本质是值传递,创建了一个独立的副本
- 总结:
auto退化而decltype不退化- 设计目的不同
auto:变量声明,模仿函数参数传递
decltype:类型查询,必须精确 - 语义不同
auto:创建副本(退化是副作用)
decltype:查询类型(不能有副作用) - 历史原因
auto:继承模板推导规则(一致性)
decltype:新引入的类型查询工具(独立设计)
- 设计目的不同
声明:本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ C++14_第二篇06/29
- ♥ Soui应用 动画二06/27
- ♥ 51CTO:Linux C++网络编程五08/20
- ♥ C++_volatile10/08
- ♥ C++_trunk相关10/24
- ♥ Protobuf记述与使用01/24