[C++11] Wrapper

C++11 - Wrappers


insert image description here

1. Function wrapper

1. Introduction of function wrapper

Look at this line of code:

ret = func(x);

What could be the above func? So func might be the function name? function pointer? Function objects (functor objects)? Also maybe a lambda expression object? So these are callable types! Such rich types can lead to inefficient templates! why? Let's continue reading

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, 2.2) << endl;
	//函数对象
	cout << useF(Functor(), 2.2) << endl;
	//lambda表达式
	cout << useF([](double d)->double{
     
      return d / 4; }, 2.2) << endl;
	return 0;
}

image-20230418172806929

Through the above program verification, we will find that the useF function template is instantiated three times. The address of each count is different. Is there a way to instantiate only one copy of the useF function template? In order to unify callable types, C++11 introduces a function wrapper to solve the problem.


2. The concept of function wrapper

Function wrappers are also called adapters. The function in C++ is essentially a class template and a wrapper.

The prototype of the function class template is as follows:

//std::function在头文件<functional>
// 类模板原型如下
template <class T> function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;

Template parameter description:

  • Ret : the return type of the called function
  • Args...: the formal parameters of the called function

With the function wrapper, callable objects can be wrapped, including function pointers (function names), functors (function objects), lambda expressions, and member functions of classes. Example:

#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;
	}
};
int main()
{
     
     
	// 函数名(函数指针)
	std::function<int(int, int)> func1 = f;
	cout << func1(1, 2) << endl;//3
	// 仿函数
	std::function<int(int, int)> func2 = Functor();
	cout << func2(1, 2) << endl;//3
	// lambda表达式
	std::function<int(int, int)> func3 = [](int a, int b) {
     
     return a + b; };
	cout << func3(1, 2) << endl;//3
	//类的静态成员函数
	std::function<int(int, int)> func4 = Plus::plusi;//非静态成员函数必须加&,静态可不加
	cout << func4(1, 2) << endl;//3
	//类的非静态成员函数
	std::function<double(Plus, double, double)> func5 = &Plus::plusd;//非静态成员函数必须加&,静态可不加
	cout << func5(Plus(), 1.1, 2.2) << endl;//3.3
	return 0;
}

Notice:

  1. Remember to specify the class domain when wrapping the member functions of the class
  2. The non-static member function of the class must add &, and the static member function can be added or not
  3. There is an implicit this pointer in the non-static member function of the class, and an additional parameter (class name) should be added when packaging, and an anonymous object of the class should be passed when calling

3. Use function wrappers to optimize the evaluation of reverse Polish expressions

Look at such a question on leetcode: Reverse Polish expression evaluation

The steps to solve this problem are as follows:

  • Define a stack and traverse this array
  • Traverse to the number and push it directly into the stack
  • If the string traversed is not a number (any one of "+", "-", "*", "/"), take the two elements on the top of the stack to perform the corresponding arithmetic operation, and push the result into stack.
  • At the end of the traversal, the element at the top of the final stack is the final value

code show as below:

class Solution {
     
     
public:
    int evalRPN(vector<string>& tokens) {
     
     
        stack<int> st;
        for (int i = 0; i < tokens.size(); i++)
        {
     
     
            if (!(tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/"))
            {
     
     
                st.push(stoi(tokens[i]));
            }
            else
            {
     
     
                int num1 = st.top();
                st.pop();
                int num2 = st.top();
                st.pop();
                if (tokens[i] == "+")
                    st.push(num1 + num2);
                else if (tokens[i] == "-")
                    st.push(num2 - num1);
                else if (tokens[i] == "*")
                    st.push((long)num1 * num2);
                else if (tokens[i] == "/")
                    st.push(num2 / num1);
            }
        }
        return st.top();
    }
};
  • In the above code, I mainly use the if and else statements to determine what kind of operation it performs. If the type of subsequent operations increases, then the if and else statements must be added, which is somewhat redundant. We can use wrappers to optimize this code .

The optimization rules are as follows:

  1. Here we can use the wrapper to establish the mapping relationship between the operator and the corresponding function. This step requires the use of a map container to complete the corresponding function mapping relationship for each operator in the list initialization.
  2. Traverse the array to determine whether the character is in the map container. If it is, it means it is an operator. Take the two elements at the top of the stack, perform corresponding operations, and push the result to the stack.
  3. If the character is not in the map container, it means it is an operand, and it is directly pushed onto the stack
  4. After the traversal ends, the element at the top of the stack is returned directly
  5. Note that to prevent stack overflow, use long long type
class Solution {
     
     
public:
    int evalRPN(vector<string>& tokens) {
     
     
        stack<long long> st;
        map<string, function<long long(long long, long long)>> opFuncMap = 
        {
     
     
            {
     
     "+", [](long long x, long long y){
     
     return x + y;}},
            {
     
     "-", [](long long x, long long y){
     
     return x - y;}},
            {
     
     "*", [](long long x, long long y){
     
     return x * y;}},
            {
     
     "/", [](long long x, long long y){
     
     return x / y;}}
        };
        for (auto& str : tokens)
        {
     
     
            if (opFuncMap.count(str))//判断操作符是否在opFuncMap
            {
     
     
                long long right = st.top();
                st.pop();
                long long left = st.top();
                st.pop();
                st.push(opFuncMap[str](left, right));
            }
            else//操作数
            {
     
     
                st.push(stoi(str));
            }
        }
        return st.top();
    }
};

4. Use the function wrapper to solve the problem of low template efficiency and instantiation of multiple copies

When we introduced the wrapper at the beginning, we proposed to pass different types and instantiate three copies, resulting in low efficiency of the template. Now I no longer directly pass function pointers, lambda... Instead, wrap it with a wrapper Solved the problem:

#include <functional>
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;
	// lamber表达式
	std::function<double(double)> func3 = [](double d)->double {
     
      return d / 4; };
	cout << useF(func3, 11.11) << endl;
	return 0;
}

image-20230418174438896

At this time, you will find that only one copy is instantiated, and the address of count is always the same, so the static member variables are naturally accumulating. It can be seen that the wrapper unifies the type.


5. The meaning of function wrapper

  • Unify the types of callable objects so that we can manage them uniformly.
  • After packaging, the return value and formal parameter types of the callable object are clarified, which is more convenient for users to use.

Two, bind wrapper

1. Introduction of bind wrapper

Look at the following code:

#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:
	Plus(int x = 2)
		:_x(x)
	{
     
     }
	int plusi(int a, int b)
	{
     
     
		return (a + b) * _x;
	}
private:
	int _x;
};

Non-static member functions have an implicit this pointer, so one more parameter (3 in total) is passed when wrapping the plusi function. This is what we have said before, while Functor and f only need to pass 2 parameters. Can:

int main()
{
     
     
	// 函数名(函数指针)
	std::function<int(int, int)> func1 = f;
	cout << func1(1, 2) << endl;//3
	// 仿函数
	std::function<int(int, int)> func2 = Functor();
	cout << func2(10, 20) << endl;//30
	//类的非静态成员函数
	std::function<double(Plus, int, int)> func3 = &Plus::plusi;//非静态成员函数必须加&,静态可不加
	cout << func3(Plus(), 100, 200) << endl;//300.22
}

Suppose I use the wrapper to establish the mapping relationship between the string and the corresponding function and put it in the map container. At this time, there will be a problem: the member function will have three parameters, and the value position in my map container only allows two parameters to be passed. parameters, causing the parameters to fail to match:

image-20230427200302431

In order to solve this problem, we need to use our bind wrapper below.


2. The concept of bind wrapper

bind wrapper concept:

  • The std::bind function is defined in the header file <functional> and is a function template. It is like a function wrapper (adapter) that accepts a callable object and generates a new callable object to "adapt "The parameter list of the original object. Generally speaking, we can use it to take a function fn that originally received N parameters, and return a new function that receives M parameters (M can be greater than N, but it doesn't make sense to do so) by binding some parameters. At the same time, using the std::bind function can also implement operations such as parameter order and number adjustment.

The bind function template prototype is as follows:

template <class Fn, class... Args>
/* unspecified */ bind(Fn&& fn, Args&&... args);
template <class Ret, class Fn, class... Args>
/* unspecified */ bind(Fn&& fn, Args&&... args);

Template parameter description:

  • fn: callable object
  • args...: list of arguments to bind (values ​​or placeholders)

The general form of calling bind:

auto newCallable = bind(callable,arg_list);

explain:

  • callable: The callable object that needs to be wrapped.
  • newCallable: itself is a callable object
  • arg_list: is a comma-separated list of parameters corresponding to the parameters of the given callable. When we call newCallable, newCallable will call callable and pass it the parameters in arg_list.

The parameters in arg_list may contain names of the form _ n, where n is an integer. These parameters are "placeholders" that represent the parameters of newCallable, and they occupy the "position" of the parameters passed to newCallable. The value n indicates the position of the parameter in the resulting callable object: _1 is the first parameter of newCallable, _2 is the second parameter, and so on.


3. The bind wrapper binds and adjusts the number of parameters

Binding ordinary functions:

int Plus(int a, int b)
{
     
     
	return a + b;
}
int main()
{
     
     
	//表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定
	std::function<int(int, int)> func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
    	//auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
	cout << func1(1, 2) << endl;//3
}
  • When binding, the first parameter is passed to the callable object of the function pointer, but the subsequent parameter list to be bound is placeholders::_ 1 and placeholders::_ 2, which means that the newly generated callable object is called in subsequent calls , the first parameter passed in is passed to placeholders::_ 1, and the second parameter passed in is passed to placeholders::_ 2. At this time, the parameter passing method of the new callable object generated after binding is the same as that of the original unbound callable object, which can also be called meaningless binding.

Adjust the number of parameters:

  • I can fix the second parameter of the Plus function to 20, just set the placeholders::_2 of the parameter list to 20. At this time, when calling the newly generated callable object after binding, I only need to pass in a parameter. as follows:
int Plus(int a, int b)
{
     
     
	return a + b;
}
int main()
{
     
     
	//表示绑定函数 plus 的第2个参数为20
    std::function<int(int)> func2 = std::bind(Plus, placeholders::_1, 20);
	cout << func2(5) << endl;//25
}

Binding member functions:

class Plus
{
     
     
public:
	Plus(int x = 2)
		:_x(x)
	{
     
     }
	int plusi(int a, int b)
	{
     
     
		return (a + b) * _x;
	}
private:
	int _x;
};
int main()
{
     
     
	//未绑定,需要传3个参数
	std::function<int(Plus, int, int)> func1 = &Plus::plusi;
	cout << func1(Plus(), 100, 200) << endl;//600
	//绑定后,仅需传2个参数
	std::function<int(int, int)> func2 = std::bind(&Plus::plusi, Plus(10), placeholders::_1, placeholders::_2);
	cout << func2(100, 200) << endl;//3000
	//绑定指定参数,func3的第一个参数已被指定,仅需传1个参数
	std::function<int(int)> func3 = std::bind(&Plus::plusi, Plus(10), 15, placeholders::_1);
	cout << func3(25) << endl;//400
	return 0;
}
  • Through the comparison of func1, func2, and func3, it is obvious that my func1 is directly packaged, and I need to pass 3 parameters (one of which is an implicit this pointer). After the binding of func2 is adjusted, that is, I bind Plus() and call Sometimes it will help us to pass an anonymous object to this pointer, so we only need to pass 2 parameters; my func3 is to bind and adjust the specified parameters, the first parameter of func3 has been specified as 15, and only 1 parameter needs to be passed. Can.

Let's solve the problem that the number of parameters does not match when the bind is introduced at the beginning:

image-20230427200302431

To solve this problem, we only need to use binding to adjust the number of parameters:

int f(int a, int b)
{
     
     
	return a + b;
}
struct Functor
{
     
     
public:
	int operator() (int a, int b)
	{
     
     
		return a + b;
	}
};
class Plus
{
     
     
public:
	Plus(int x = 2)
		:_x(x)
	{
     
     }
	int plusi(int a, int b)
	{
     
     
		return (a + b) * _x;
	}
private:
	int _x;
};
int main()
{
     
     
	map<string, std::function<int(int, int)>> opFuncMap =
	{
     
     
		{
     
      "普通函数指针", f },
		{
     
      "函数对象", Functor()},
		{
     
      "成员函数指针", std::bind(&Plus::plusi, Plus(10), placeholders::_1, placeholders::_2)}
	};
	cout << opFuncMap["普通函数指针"](1, 2) << endl;//3
	cout << opFuncMap["函数对象"](10, 20) << endl;//30
	cout << opFuncMap["成员函数指针"](100, 200) << endl;//3000
	return 0;
}

At this time, after the member function pointer binding is adjusted, that is, Plus() is bound, and when calling, it will help us pass an anonymous object to the this pointer, and only need to pass 2 parameters, and there will be no parameter mismatch. situation.


4.bind wrapper binding to adjust parameter order

For the following member function sub of the Sub class, the first parameter is the implicit this pointer. If we want to call the Sub member function without using the object to call the this pointer, we can bind the first parameter of the sub member function fixedly Defined as a Sub object. for example:

class Sub
{
     
     
public:
	int sub(int a, int b)
	{
     
     
		return a - b;
	}
};
int main()
{
     
     
	std::function<int(int, int)> func1 = std::bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);
	cout << func1(1, 2) << endl;//-1
	return 0;
}
  • At this point, you only need to pass two parameters for subtraction, because it will help us pass an anonymous object to this pointer when calling.

Next, if I want to exchange the order of the two subtraction parameters, I only need to exchange the positions of placeholders::_ 1 and placeholders::_ 2 when binding:

class Sub
{
     
     
public:
	int sub(int a, int b)
	{
     
     
		return a - b;
	}
};
int main()
{
     
     
	//调整顺序
	std::function<int(int, int)> func2 = std::bind(&Sub::sub, Sub(), placeholders::_2, placeholders::_1);
	cout << func2(1, 2) << endl;//1
	return 0;
}

At this time, the actual parameter 1 is passed to _2, which is the second parameter, and the actual parameter 2 is passed to _1, which is the first parameter, 2-1=1.


5. The meaning of bind wrapper

  • Bind some parameters of a function to fixed values, so that we don't need to pass some parameters when calling.
  • The order of function parameters can be flexibly adjusted.

Guess you like

Origin blog.csdn.net/m0_64224788/article/details/130477090