1. Envoltura de funciones
En C ++ 11, std::function
los contenedores de funciones se pueden crear usando std::function
Es un objeto de función general que puede almacenar, copiar y llamar a varios objetos a los que se puede llamar, incluidos punteros de función, objetos de función, expresiones lambda, etc. function
Los envoltorios también se conocen como adaptadores. function
La esencia es una plantilla de clase y un contenedor.
ret = func(x);
func
¿ Qué puede haber ahí arriba ? Entonces, func
¿tal vez el nombre de la función? puntero de función? ¿Objetos de función (objetos funtores)? ¿También tal vez un objeto de expresión Lambda? ¡Así que estos son tipos invocables! ¡Tales tipos ricos pueden conducir a plantillas ineficientes! ¿por qué? sigamos leyendo
template<class F, class T>
T useF(F f, T x) {
static int count = 0;
cout << "count:" << ++count << endl;
cout << "count:" << &count << endl;
return f(x);
}
double f(double i) {
return i / 2;
}
struct Functor {
double operator()(double d) {
return d / 3;
}
};
int main() {
// 函数名
cout << useF(f, 11.11) << endl;
// 函数对象
cout << useF(Functor(), 11.11) << endl;
// lamber表达式
cout << useF([](double d) -> double {
return d / 4; }, 11.11) << endl;
return 0;
}
La salida es la siguiente:
count:1
count:00EDC140
5.555
count:1
count:00EDC144
3.70333
count:1
count:00EDC148
2.7775
Se puede encontrar que las direcciones de los tres recuentos son diferentes, por lo que se puede encontrar que useF
la plantilla de función se ha instanciado tres veces.
Cómo usar la función
std::function在头文件<functional>
// 类模板原型如下
template <class T> function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参
#include <functional>
int f(int a, int b){
return a + b;
}
struct Functor{
public:
int operator() (int a, int b){
return a + b;
}
};
class Plus{
public:
static int plusi(int a, int b){
return a + b;
}
double plusd(double a, double b){
return a + b;
}
};
1. Puntero de función
// 函数名(函数指针)
std::function<int(int, int)> func1 = f;
cout << func1(1, 2) << endl;
En este ejemplo, creamos un envoltorio de función f
pasando un puntero de función al constructor de . Dado que es un puntero de función, su tipo coincide, toma dos argumentos de tipo y devuelve un valor de tipo. Podemos llamar a la función envuelta y pasar parámetros.std::function
func1
f
std::function<int(int, int)>
int
int
func1
2. Objeto de función
// 函数对象
std::function<int(int, int)> func2 = Functor();
cout << func2(1, 2) << endl;
En este ejemplo, creamos una Functor
instancia del objeto de función y se la pasamos al std::function
constructor de la función para crear el contenedor de función func2
. Dado que Functor
es un objeto invocable (que define operator()
la función), su tipo std::function<int(int, int)>
coincide con . Podemos func2
llamar al objeto de función envuelto y pasar parámetros.
3. Expresiones lambda
// Lambda表达式
std::function<int(int, int)> func3 = [](const int a, const int b) {
return a + b; };
cout << func3(1, 2) << endl;
En este ejemplo, creamos una función anónima usando una expresión Lambda y la pasamos al std::function
constructor de la función para crear el contenedor de función func3
. El compilador deduce el tipo de la expresión lambda y std::function<int(int, int)>
coincide con . Podemos func3
llamar a la función Lambda envuelta con y pasar parámetros.
4. Funciones miembro estáticas de una clase
// 类的静态成员函数
std::function<int(int, int)> func4 = &Plus::plusi;
cout << func4(1, 2) << endl;
En este ejemplo, usamos &Plus::plusi
para obtener un puntero a Plus
la función miembro estática de una clase plusi
y pasarlo al std::function
constructor para crear un contenedor de función func4
. Las funciones miembro estáticas no dependen de una instancia de la clase, por lo que no se requiere ningún parámetro de objeto adicional. Podemos func4
llamar a la función de miembro estático envuelto y pasar parámetros.
5. Funciones miembro ordinarias de una clase
//类的普通成员函数
std::function<double(Plus, double, double)> func5 = &Plus::plusd;
cout << func5(Plus(), 1.1, 2.2) << endl;
En este ejemplo, creamos contenedores de función &Plus::plusd
para obtener un puntero a Plus
una función miembro normal de la clase plusd
y pasarlo al constructor de la clase . Las funciones miembro ordinarias dependen de la instancia de la clase , por lo que necesitamos pasar un objeto de clase adicional como parámetro . Podemos llamar a la función miembro ordinaria envuelta a través de y pasar el objeto de clase y otros parámetros.std::function
func5
func5
Con un contenedor, ¿cómo resolver el problema de la baja eficiencia de las plantillas y la instanciación de múltiples copias?
#include <functional>
#include <iostream>
using namespace std;
template<class F, class T>
T useF(F f, T x) {
static int count = 0;
cout << "count:" << ++count << endl;
cout << "count:" << &count << endl;
return f(x);
}
double f(double i) {
return i / 2;
}
struct Functor {
double operator()(double d) {
return d / 3;
}
};
int main() {
// 函数名
std::function<double(double)> func1 = f;
cout << useF(func1, 11.11) << endl;
// 函数对象
std::function<double(double)> func2 = Functor();
cout << useF(func2, 11.11) << endl;
// Lambda表达式
std::function<double(double)> func3 = [](double d) -> double {
return d / 4; };
cout << useF(func3, 11.11) << endl;
return 0;
}
Resultado de salida:
count:1
count:0071E4C8
5.555
count:2
count:0071E4C8
3.70333
count:3
count:0071E4C8
2.7775
Como resultado, se puede encontrar que las tres direcciones de conteo son las mismas y solo function
se instancia una copia.
Algunos otros escenarios para envoltorios:
Usando la solución anterior del contenedor:
class Solution {
public:
int evalRPN(vector<string> &tokens) {
stack<int> st;
for (auto &str: tokens) {
if (str == "+" || str == "-" || str == "*" || str == "/") {
int right = st.top();
st.pop();
int left = st.top();
st.pop();
switch (str[0]) {
case '+':
st.push(left + right);
break;
case '-':
st.push(left - right);
break;
case '*':
st.push(left * right);
break;
case '/':
st.push(left / right);
break;
}
} else {
// 1、atoi itoa
// 2、sprintf scanf
// 3、stoi to_string C++11
st.push(stoi(str));
}
}
return st.top();
}
};
Una solución usando un contenedor:
class Solution {
public:
int evalRPN(vector<string> &tokens) {
stack<long> st;
unordered_map<string, function<long(long, long)>> opFuncMap = {
{
"+", [](long x, long y) -> long {
return x + y; }},
{
"-", [](long x, long y) -> long {
return x - y; }},
{
"*", [](long x, long y) -> long {
return x * y; }},
{
"/", [](long x, long y) -> long {
return x / y; }}};
for (auto &str: tokens) {
if (opFuncMap.find(str) != opFuncMap.end()) {
long right = st.top();
st.pop();
long left = st.top();
st.pop();
st.push(opFuncMap[str](left, right));
} else {
st.push(stoll(str));
}
}
return st.top();
}
};
2. enlazar
std::bind
es una plantilla de función en la biblioteca estándar de C++, ubicada <functional>
en el archivo de encabezado. Se utiliza para crear un objeto de función (también conocido como enlazador de funciones), que puede vincular un objeto invocable con sus parámetros para formar un nuevo objeto invocable.
El prototipo es el siguiente:
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
// with return type (2)
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
Piense en bind
una función como un adaptador de función genérico que toma un objeto invocable y produce un nuevo objeto invocable que "se ajusta" a la lista de argumentos del objeto original. La forma general de llamar a bind:auto newCallable = bind(callable,arg_list);
donde newCallable
sí mismo es un objeto invocable y arg_list
es una lista separada por comas de argumentos correspondientes a los callable
argumentos dados. Cuando llamemos newCallable
, newCallable
será llamado callable
y pasado a arg_list
los parámetros en él.
arg_list
Los parámetros en pueden contener nombres de la forma n, donde n es un número entero, y estos parámetros son "marcadores de posición", que denotan newCallable
parámetros que ocupan newCallable
la "posición" de los parámetros pasados a . El valor n indica la posición del parámetro en el objeto invocable resultante: 1 para el newCallable
primer parámetro de _2 para el segundo parámetro y así sucesivamente.
1. Puntero de función de enlace
#include <functional>
#include <iostream>
void foo(int a, int b) {
std::cout << "Sum: " << (a + b) << std::endl;
}
int main() {
auto boundFunc = std::bind(foo, 10, 20);
boundFunc(); // 调用绑定的函数指针
return 0;
}
std::bind
Vincula el puntero de función foo
con los parámetros 10
y 20
crea un nuevo objeto de función boundFunc
. La llamada boundFunc()
invoca la función original foo
, pasando los parámetros enlazados. La salida es "Suma: 30".
2. Funciones miembro vinculantes
#include <functional>
#include <iostream>
class MyClass {
public:
void printSum(int a, int b) {
std::cout << "Sum: " << (a + b) << std::endl;
}
};
int main() {
MyClass obj;
auto boundFunc = std::bind(&MyClass::printSum, &obj, 10, 20);
boundFunc(); // 调用绑定的成员函数
return 0;
}
std::bind
Al vincular la función miembro printSum
con el puntero del objeto &obj
y los parámetros 10
, 20
se crea un nuevo objeto de función boundFunc
. Call boundFunc()
llama a la función miembro original printSum
, pasando el objeto enlazado y los parámetros. La salida es "Suma: 30".
3. Funciones de miembros vinculantes y marcadores de posición
#include <functional>
#include <iostream>
class MyClass {
public:
void printSum(int a, int b) {
std::cout << "Sum: " << (a + b) << std::endl;
}
};
int main() {
MyClass obj;
auto boundFunc = std::bind(&MyClass::printSum, &obj, std::placeholders::_1, 20);
boundFunc(10); // 调用绑定的成员函数,传递部分参数
return 0;
}
std::bind
Vincular la función miembro printSum
con el puntero del objeto &obj
y un marcador de posición std::placeholders::_1
(para el primer parámetro) y la constante 20
crea un nuevo objeto de función boundFunc
. Call boundFunc(10)
llama a la función miembro original printSum
, pasándola 10
como primer argumento y la constante enlazada 20
como segundo argumento. La salida es "Suma: 30".
4. Reordenación de parámetros y parámetros predeterminados
#include <functional>
#include <iostream>
void foo(int a, int b, int c) {
std::cout << "Sum: " << (a + b * c) << std::endl; //230
}
int main() {
auto boundFunc = std::bind(foo, std::placeholders::_3, std::placeholders::_1, std::placeholders::_2);
boundFunc(10, 20, 30); // 调用绑定的函数,重排参数顺序
return 0;
}
std::bind
Vincular la función foo
con los marcadores de posición y std::placeholders::_3
crea un nuevo objeto de función . La llamada llamará a la función original y los parámetros se pasan en el orden reorganizado, a está vinculado al tercer parámetro 30, b está vinculado al primer parámetro 10 y c está vinculado al segundo parámetro 20. Eso es 30+10*20 = 230.std::placeholders::_1
std::placeholders::_2
boundFunc
boundFunc(10, 20, 30)
foo
std::bind
También admite configuraciones predeterminadas para parámetros vinculados, por ejemplo std::bind(foo, _1, _2, 100)
, donde 100
se usa una constante fija como parámetro predeterminado.
Usar la función y unir
Ejemplo de uso
#include <functional>
#include <iostream>
#include <map>
#include <string>
using namespace std;
using namespace placeholders;
int Plus(int a, int b) {
return a + b;
}
int Mul(int a, int b, int c, double rate) {
return a + b + c * rate;
}
class Sub {
public:
int sub(int a, int b) {
return a - b;
}
};
int main() {
// 调整顺序 -- 鸡肋
// _1 _2.... 定义在placeholders命名空间中,代表绑定函数对象的形参,
// _1,_2...分别代表第一个形参、第二个形参...
function<int(int, int)> funcPlus = Plus;
//function<int(Sub, int, int)> funcSub = &Sub::sub;
function<int(int, int)> funcSub = bind(&Sub::sub, Sub(), _2, _1);
function<int(int, int, int)> funcMul = bind(Mul, _3, _1, _2, 1.5);
cout << funcPlus(1, 2) << endl; //3
cout << funcSub(1, 2) << endl; //1
cout << funcMul(3, 2, 1) << endl;//a = 1, b = 3 c = 2 * 1.5 1+3+2*1.5=7
return 0;
}
int main() {
map<string, function<int(int, int)>> opFuncMap = {
{
"+", Plus},
{
"-", bind(&Sub::sub, Sub(), _1, _2)},
{
"*", bind(Mul, _2, _1, 1, 2)},//这里只能有两个占位符,function的参数只有2个
};
cout << opFuncMap["+"](1, 2) << endl;//3
cout << opFuncMap["-"](1, 2) << endl;//-1
cout << opFuncMap["*"](2, 3) << endl;//2+3+1*2=7
}