std::invoke,通用的函数调用器
std::invoke
是 C++17标准库中引入的一个函数模板,用于统一地调用可调用对象(函数、函数指针、成员函数指针、仿函数等)。它解决了在 C++ 中调用可调用对象的一致性和灵活性问题。
在之前的 C++ 版本中,要调用不同类型的可调用对象,需要使用不同的语法,例如使用函数调用运算符 () 来调用函数或函数指针,使用成员访问运算符 -> 或 . 来调用成员函数。这样的语法差异导致了代码的冗余和不一致,给编写和维护代码带来了困扰。
std::invoke
的引入就是为了解决这个问题,它提供了一种统一的调用语法,无论是调用函数、函数指针、成员函数指针还是仿函数,都可以使用相同的方式进行调用。
std::invoke
的语法如下:
template <typename Fn, typename... Args>
decltype(auto) invoke(Fn&& fn, Args&&... args);
它接受一个可调用对象 fn 和相应的参数 args...,并返回调用结果。使用方法如下:
#include <iostream>
#include <functional>
struct Foo {
int add(int a, int b) {
return a + b;
}
};
int multiply(int a, int b) {
return a * b;
}
int main() {
Foo foo;
// 使用 std::invoke 调用成员函数
int result1 = std::invoke(&Foo::add, foo, 3, 4);
std::cout << "Result 1: " << result1 << std::endl; // 输出: Result 1: 7
// 使用 std::invoke 调用自由函数
int result2 = std::invoke(multiply, 3, 4);
std::cout << "Result 2: " << result2 << std::endl; // 输出: Result 2: 12
int result3=std::invoke([](int a,int b){ return a+b;},519,1);
std::cout<<"result3: "<<result3<<'\n';
return 0;
}
通过 std::invoke,我们可以在不关心可调用对象的具体类型的情况下进行调用,提高了代码的灵活性和可读性。它尤其适用于泛型编程中需要以统一方式调用各种可调用对象的场景,例如使用函数指针或成员函数指针作为模板参数的算法或容器等。
另外,std::invoke 还考虑了完美转发的问题,可以正确地将参数转发给可调用对象,并返回适当的引用类型。
所以这就是个语法糖,可以让你的代码看起来风格统一,更易于维护,但是真的是这样吗,也没有多易懂啊?不就是把调用方式统一了吗?当然没这么简单。他还支持多态:
#include <iostream>
#include <functional>
class Shape {
public:
virtual void draw() const = 0;
};
class Circle : public Shape {
public:
void draw() const override {
std::cout << "Drawing a circle." << std::endl;
}
};
class Square : public Shape {
public:
void draw() const override {
std::cout << "Drawing a square." << std::endl;
}
};
int main() {
Circle circle;
Square square;
Shape* shape1 = &circle;
Shape* shape2 = □
// 使用 std::invoke 多态调用 draw 函数
std::invoke(&Shape::draw, shape1); // 输出: Drawing a circle.
std::invoke(&Shape::draw, shape2); // 输出: Drawing a square.
return 0;
}
我们定义了一个基类 Shape
,并派生了两个子类 Circle
和 Square
。这些类都实现了 draw 函数。然后,我们使用指针将 circle
和 square
对象转换为基类指针,并使用 std::invoke
多态调用了 draw
函数。通过 std::invoke
,我们可以在运行时选择不同的子类对象,并以统一的方式调用其虚函数。在使用 std::invoke
进行多态调用时,需要确保函数指针或成员指针的类型与基类中的虚函数匹配,以正确调用派生类的函数。也就是多态实现的必要条件他会根据基类指针指向的实际类型去对应虚表里面找。
在网上找到一份源码:
template <typename F, typename T, typename... Args>
decltype(auto) invoke(F&& f, T&& t, Args&&... args) {
if constexpr (std::is_member_function_pointer_v<std::remove_reference_t<F>>) {
if constexpr (std::is_base_of_v<std::remove_reference_t<F>, std::remove_reference_t<T>>) {
return (std::forward<T>(t).*f)(std::forward<Args>(args)...);
} else {
return ((*std::forward<T>(t)).*f)(std::forward<Args>(args)...);
}
} else if constexpr (std::is_member_object_pointer_v<std::remove_reference_t<F>>) {
if constexpr (std::is_base_of_v<std::remove_reference_t<F>, std::remove_reference_t<T>>) {
return std::forward<T>(t).*f;
} else {
return (*std::forward<T>(t)).*f;
}
} else {
return std::forward<F>(f)(std::forward<T>(t), std::forward<Args>(args)...);
}
}
还是挺复杂的,使用了很多RTTI特性,还有条件编译,都是新特性,用于判断传入的是什么类型,比如成员函数肯定得和普通函数分开,通过上面使用,毕竟参数都不一样,调用成员函数第二个参数是对象,但是发现没有?这货在进行判断的时候会区分两种对象
std::is_member_function_pointer_v<std::remove_reference_t<F>>
std::is_member_object_pointer_v<std::remove_reference_t<F>>y
一是成员函数指针,指向成员函数,然后判断是否基类,判断是否多态调用,二是成员对象指针,要知道成员函数和成员对象是不一样的(函数不占内存什么的),也就是说,我们可以做这样的事:
#include <iostream>
#include <functional>
class MyClass {
public:
int memberVariable = 10;
void memberFunction() {
std::cout << "Member function called!" << std::endl;
}
};
int main() {
MyClass obj;
// 使用 std::invoke 调用成员函数指针
void (MyClass::*funcPtr)() = &MyClass::memberFunction;
std::invoke(funcPtr, obj);
// 使用 std::invoke 访问成员对象指针
int MyClass::*varPtr = &MyClass::memberVariable;
int value = std::invoke(varPtr, obj);
std::cout << "Value: " << value << std::endl;
return 0;
}
可以通过std::invoke
,访问成员变量, 他确实不是这么的简单,标准库的目的旨在提供一个调用模板,可以访问所有的东西,所有函数,所有对象成员,所有的所有,但是结果差强人意,没人用,不如直接用调用运算符,更简洁,这就是今天的内容。
喜欢的同学点赞收藏呗,每天更新!
祝大家秋招斩获满意的offer知乎!
#STL#