天天看點

C++11——— 包裝器

文章目錄

  • ​​包裝器​​
  • ​​function包裝器​​
  • ​​function包裝器介紹​​
  • ​​function包裝器統一類型​​
  • ​​function包裝器簡化代碼​​
  • ​​function包裝器的意義​​
  • ​​bind包裝器​​
  • ​​bind包裝器介紹​​
  • ​​bind包裝器綁定固定參數​​
  • ​​bind包裝器調整傳參順序​​
  • ​​bind包裝器的意義​​

包裝器

function包裝器

function包裝器介紹

function包裝器

function是一種函數包裝器,也叫做擴充卡。它可以對可調用對象進行包裝,C++中的function本質就是一個類模闆。

function類模闆的原型如下:

template <class T> function;     // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;      

模闆參數說明:

  • ​Ret​

    ​:被包裝的可調用對象的傳回值類型。
  • ​Args...​

    ​:被包裝的可調用對象的形參類型。
包裝示例

function包裝器可以對可調用對象進行包裝,包括函數指針(函數名)、仿函數(函數對象)、lambda表達式、類的成員函數。比如:

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()
{
  //1、包裝函數指針(函數名)
  function<int(int, int)> func1 = f;
  cout << func1(1, 2) << endl;

  //2、包裝仿函數(函數對象)
  function<int(int, int)> func2 = Functor();
  cout << func2(1, 2) << endl;

  //3、包裝lambda表達式
  function<int(int, int)> func3 = [](int a, int b){return a + b; };
  cout << func3(1, 2) << endl;

  //4、類的靜态成員函數
  //function<int(int, int)> func4 = Plus::plusi;
  function<int(int, int)> func4 = &Plus::plusi; //&可省略
  cout << func4(1, 2) << endl;

  //5、類的非靜态成員函數
  function<double(Plus, double, double)> func5 = &Plus::plusd; //&不可省略
  cout << func5(Plus(), 1.1, 2.2) << endl;
  return 0;
}      

注意事項:

  • 包裝時指明傳回值類型和各形參類型,然後将可調用對象指派給function包裝器即可,包裝後function對象就可以像普通函數一樣使用了。
  • 取靜态成員函數的位址可以不用取位址運算符“&”,但取非靜态成員函數的位址必須使用取位址運算符“&”。
  • 包裝非靜态的成員函數時需要注意,非靜态成員函數的第一個參數是隐藏this指針,是以在包裝時需要指明第一個形參的類型為類的類型。

function包裝器統一類型

對于以下函數模闆useF:

  • 傳入該函數模闆的第一個參數可以是任意的可調用對象,比如函數指針、仿函數、lambda表達式等。
  • useF中定義了靜态變量count,并在每次調用時将count的值和位址進行了列印,可判斷多次調用時調用的是否是同一個useF函數。

代碼如下:

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;

  //lambda表達式
  cout << useF([](double d)->double{return d / 4; }, 11.11) << endl;
  return 0;
}      

由于函數指針、仿函數、lambda表達式是不同的類型,是以useF函數會被執行個體化出三份,三次調用useF函數所列印count的位址也是不同的。

  • 但實際這裡根本沒有必要執行個體化出三份useF函數,因為三次調用useF函數時傳入的可調用對象雖然是不同類型的,但這三個可調用對象的傳回值和形參類型都是相同的。
  • 這時就可以用包裝器分别對着三個可調用對象進行包裝,然後再用這三個包裝後的可調用對象來調用useF函數,這時就隻會執行個體化出一份useF函數。
  • 根本原因就是因為包裝後,這三個可調用對象都是相同的function類型,是以最終隻會執行個體化出一份useF函數,該函數的第一個模闆參數的類型就是function類型的。

代碼如下:

int main()
{
  //函數名
  function<double(double)> func1 = func;
  cout << useF(func1, 11.11) << endl;

  //函數對象
  function<double(double)> func2 = Functor();
  cout << useF(func2, 11.11) << endl;

  //lambda表達式
  function<double(double)> func3 = [](double d)->double{return d / 4; };
  cout << useF(func3, 11.11) << endl;
  return 0;
}      

這時三次調用useF函數所列印count的位址就是相同的,并且count在三次調用後會被累加到3,表示這一個useF函數被調用了三次。

function包裝器簡化代碼

求解逆波蘭表達式的步驟如下:

  • 定義一個棧,依次周遊所給字元串。
  • 如果周遊到的字元串是數字則直接入棧。
  • 如果周遊到的字元串是加減乘除運算符,則從棧定抛出兩個數字進行對應的運算,并将運算後得到的結果壓入棧中。
  • 所給字元串周遊完畢後,棧頂的數字就是逆波蘭表達式的計算結果。

代碼如下:

class Solution {
public:
  int evalRPN(vector<string>& tokens) {
    stack<int> st;
    for (const auto& str : tokens)
    {
      int left, right;
      if (str == "+" || str == "-" || str == "*" || str == "/")
      {
        right = st.top();
        st.pop();
        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;
        default:
          break;
        }
      }
      else
      {
        st.push(stoi(str));
      }
    }
    return st.top();
  }
};      

在上述代碼中,我們通過switch語句來判斷本次需要進行哪種運算,如果運算類型增加了,比如增加了求餘、幂、對數等運算,那麼就需要在switch語句的後面中繼續增加case語句。

這種情況可以用包裝器來簡化代碼。

  • 建立各個運算符與其對應需要執行的函數之間的映射關系,當需要執行某一運算時就可以直接通過運算符找到對應的函數進行執行。
  • 當運算類型增加時,就隻需要建立新增運算符與其對應函數之間的映射關系即可。

代碼如下:

class Solution {
public:
  int evalRPN(vector<string>& tokens) {
    stack<int> st;
    unordered_map<string, function<int(int, int)>> opMap = {
      { "+", [](int a, int b){return a + b; } },
      { "-", [](int a, int b){return a - b; } },
      { "*", [](int a, int b){return a * b; } },
      { "/", [](int a, int b){return a / b; } }
    };
    for (const auto& str : tokens)
    {
      int left, right;
      if (str == "+" || str == "-" || str == "*" || str == "/")
      {
        right = st.top();
        st.pop();
        left = st.top();
        st.pop();
        st.push(opMap[str](left, right));
      }
      else
      {
        st.push(stoi(str));
      }
    }
    return st.top();
  }
};      

需要注意的是,這裡建立的是運算符與function類型之間的映射關系,是以無論是函數指針、仿函數還是lambda表達式都可以在包裝後與對應的運算符進行綁定。

function包裝器的意義

  • 将可調用對象的類型進行統一,便于我們對其進行統一化管理。
  • 包裝後明确了可調用對象的傳回值和形參類型,更加友善使用者使用。

bind包裝器

bind包裝器介紹

bind包裝器

bind也是一種函數包裝器,也叫做擴充卡。它可以接受一個可調用對象,生成一個新的可調用對象來“适應”原對象的參數清單,C++中的bind本質是一個函數模闆。

bind函數模闆的原型如下:

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

模闆參數說明:

  • ​fn​

    ​:可調用對象。
  • ​args...​

    ​:要綁定的參數清單:值或占位符。
調用bind的一般形式

調用bind的一般形式為:​

​auto newCallable = bind(callable, arg_list);​

解釋說明:

  • ​callable​

    ​:需要包裝的可調用對象。
  • ​newCallable​

    ​:生成的新的可調用對象。
  • ​arg_list​

    ​:逗号分隔的參數清單,對應給定的callable的參數。當調用newCallable時,newCallable會調用callable,并傳給它arg_list中的參數。

arg_list中的參數可能包含形如_n的名字,其中n是一個整數,這些參數是“占位符”,表示newCallable的參數,它們占據了傳遞給newCallable的參數的“位置”。數值n表示生成的可調用對象中參數的位置,比如_1為newCallable的第一個參數,_2為第二個參數,以此類推。

此外,除了用auto接收包裝後的可調用對象,也可以用function類型指明傳回值和形參類型後接收包裝後的可調用對象。

bind包裝器綁定固定參數

無意義的綁定

下面這種綁定就是無意義的綁定:

int Plus(int a, int b)
{
  return a + b;
}
int main()
{
  //無意義的綁定
  function<int(int, int)> func = bind(Plus, placeholders::_1, placeholders::_2);
  cout << func(1, 2) << endl; //3
  return 0;
}      

綁定時第一個參數傳入函數指針這個可調用對象,但後續傳入的要綁定的參數清單依次是placeholders::_1和placeholders::_2,表示後續調用新生成的可調用對象時,傳入的第一個參數傳給placeholders::_1,傳入的第二個參數傳給placeholders::_2。此時綁定後生成的新的可調用對象的傳參方式,和原來沒有綁定的可調用對象是一樣的,是以說這是一個無意義的綁定。

綁定固定參數

如果想把Plus函數的第二個參數固定綁定為10,可以在綁定時将參數清單的placeholders::_2設定為10。比如:

int Plus(int a, int b)
{
  return a + b;
}
int main()
{
  //綁定固定參數
  function<int(int)> func = bind(Plus, placeholders::_1, 10);
  cout << func(2) << endl; //12
  return 0;
}      

此時調用綁定後新生成的可調用對象時就隻需要傳入一個參數,它會将該值與10相加後的結果進行傳回。

bind包裝器調整傳參順序

調整傳參順序

對于下面Sub類中的sub成員函數,sub成員函數的第一個參數是隐藏的this指針,如果想要在調用sub成員函數時不用對象進行調用,那麼可以将sub成員函數的第一個參數固定綁定為一個Sub對象。比如:

class Sub
{
public:
  int sub(int a, int b)
  {
    return a - b;
  }
};
int main()
{
  //綁定固定參數
  function<int(int, int)> func = bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);
  cout << func(1, 2) << endl; //-1
  return 0;
}      

此時調用綁定後生成的可調用對象時,就隻需要傳入用于相減的兩個參數了,因為在調用時會固定幫我們傳入一個匿名對象給this指針。

class Sub
{
public:
  int sub(int a, int b)
  {
    return a - b;
  }
};
int main()
{
  //調整傳參順序
  function<int(int, int)> func = bind(&Sub::sub, Sub(), placeholders::_2, placeholders::_1);
  cout << func(1, 2) << endl; //1
  return 0;
}      

bind包裝器的意義

  • 将一個函數的某些參數綁定為固定的值,讓我們在調用時可以不用傳遞某些參數。
  • 可以對函數參數的順序進行靈活調整。

繼續閱讀