<C++> C++11包装器function和bind

1.function包装器

在C++11中,可以使用std::function来创建函数包装器。std::function是一个通用的函数对象,可以存储、复制和调用各种可调用对象,包括函数指针、函数对象、Lambda表达式等。function包装器也叫作适配器。function本质是一个类模板,也是一个包装器。

ret = func(x);

上面func可能是什么呢?那么func可能是函数名?函数指针?函数对象(仿函数对象)?也有可能是Lambda表达式对象?所以这些都是可调用的类型!如此丰富的类型,可能会导致模板的效率低下!为什么呢?我们继续往下看

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;
}

输出结果如下:

count:1
count:00EDC140
5.555
count:1
count:00EDC144
3.70333
count:1
count:00EDC148
2.7775

可以发现,三个count的地址都不同,因此可以发现useF函数模板实例化了三份。

function使用方式

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. 函数指针

// 函数名(函数指针)
std::function<int(int, int)> func1 = f;
cout << func1(1, 2) << endl;

在这个示例中,我们将函数f的指针传递给std::function的构造函数来创建一个函数包装器func1。由于f是一个函数指针,它的类型与std::function<int(int, int)>匹配,它接受两个int类型的参数并返回一个int类型的值。我们可以通过func1来调用包装的函数,并传递参数。

2. 函数对象

// 函数对象
std::function<int(int, int)> func2 = Functor();
cout << func2(1, 2) << endl;

在这个示例中,我们创建了一个函数对象Functor的实例,并将其传递给std::function的构造函数来创建函数包装器func2。由于Functor是一个可调用的对象(定义了operator()函数),它的类型与std::function<int(int, int)>匹配。我们可以通过func2来调用包装的函数对象,并传递参数。

3. Lambda表达式

// Lambda表达式
std::function<int(int, int)> func3 = [](const int a, const int b) {
    
     return a + b; };
cout << func3(1, 2) << endl;

在这个示例中,我们使用Lambda表达式创建了一个匿名函数,并将其传递给std::function的构造函数来创建函数包装器func3。Lambda表达式的类型由编译器推导出来,并与std::function<int(int, int)>匹配。我们可以通过func3来调用包装的Lambda函数,并传递参数。

4. 类的静态成员函数

// 类的静态成员函数
std::function<int(int, int)> func4 = &Plus::plusi;
cout << func4(1, 2) << endl;

在这个示例中,我们使用&Plus::plusi来获取Plus类的静态成员函数plusi的指针,并将其传递给std::function的构造函数来创建函数包装器func4。静态成员函数不依赖于类的实例,因此不需要额外的对象参数。我们可以通过func4来调用包装的静态成员函数,并传递参数。

5. 类的普通成员函数

//类的普通成员函数
std::function<double(Plus, double, double)> func5 = &Plus::plusd;
cout << func5(Plus(), 1.1, 2.2) << endl;

在这个示例中,我们使用&Plus::plusd来获取Plus类的普通成员函数plusd的指针,并将其传递给std::function的构造函数来创建函数包装器func5普通成员函数依赖于类的实例,因此我们需要额外传递一个类对象作为参数。我们可以通过func5来调用包装的普通成员函数,并传递类对象和其他参数。

有了包装器,如何解决模板的效率低下,实例化多份的问题呢?

#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;
}

输出结果:

count:1
count:0071E4C8
5.555
count:2
count:0071E4C8
3.70333
count:3
count:0071E4C8
2.7775

结果可以发现三个count地址都一样,通过function只实例化出了一份。

包装器的其他一些场景:

150. 逆波兰表达式求值 - 力扣(LeetCode)

使用包装器以前的解法:

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();
    }
};

使用包装器的解法:

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.bind

std::bind是C++标准库中的一个函数模板,位于<functional>头文件中。它用于创建函数对象(也称为函数绑定器),可以将一个可调用对象与其参数绑定起来,形成一个新的可调用对象。

原型如下:

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);

可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对 象来“适应”原对象的参数列表。 调用bind的一般形式:auto newCallable = bind(callable,arg_list);

其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。

arg_list中的参数可能包含形如n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置:1为newCallable的第一个参数,_2为第二个参数,以此类推。

1. 绑定函数指针

#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将函数指针foo与参数1020绑定起来,创建了一个新的函数对象boundFunc。调用boundFunc()会调用原始函数foo,并传递绑定的参数。输出结果为"Sum: 30"。

2. 绑定成员函数

#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将成员函数printSum与对象指针&obj和参数1020绑定起来,创建了一个新的函数对象boundFunc。调用boundFunc()会调用原始的成员函数printSum,并传递绑定的对象和参数。输出结果为"Sum: 30"。

3. 绑定成员函数和占位符

#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将成员函数printSum与对象指针&obj和一个占位符std::placeholders::_1(表示第一个参数)以及常数20绑定起来,创建了一个新的函数对象boundFunc。调用boundFunc(10)会调用原始的成员函数printSum,传递10作为第一个参数和绑定的常数20作为第二个参数。输出结果为"Sum: 30"。

4. 参数重排和缺省参数

#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将函数foo与占位符std::placeholders::_3std::placeholders::_1std::placeholders::_2绑定起来,创建了一个新的函数对象boundFunc。调用boundFunc(10, 20, 30)会调用原始的函数foo,参数按照重排的顺序传递,a绑定第三个参数30,b绑定第1个参数10,c绑定第二个参数20。即30+10*20 = 230。

std::bind还支持对绑定的参数进行缺省设置,例如std::bind(foo, _1, _2, 100),其中100是一个固定的常数作为缺省参数。

function和bind配合使用

使用举例

#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
}

猜你喜欢

转载自blog.csdn.net/ikun66666/article/details/131367399