C++STL中的函式物件
前言
所謂函式物件,即 Function Object ,或者稱之為仿函式(functors)。顧名思義,就是像函式的一種物件,我們可以把函式物件看作是一個函式與物件的結合,一方面,它本質上是一個物件,但主要功能是使用其成員函式(主要是operator()
)在不同的容器和函式中傳值;另一方面,它相比於普通函式作為函式引數,具有更強大的資料傳遞能力。函式物件,像函式而比函式強大,是物件,而沒有物件複雜。
C++標準庫中實現了許多高階資料結構和演算法,函式物件就是開啟標準庫之門的一把鑰匙。熟練使用函式物件在許多實用場景下具有無與倫比的優勢。
Function Object(函式物件)概念
所謂function object,是定義了一個operator()
的物件。語法如下:
// 這裡定義一個函式物件
class FunctionObjectType{
public:
void operator()(){
// TO-DO 這個函式體是函式物件的函式體
}
}
定義了之後,我們這樣使用——
FunctionObejctType fo;
fo();// 函式物件的使用
本質上,我們只是過載了一個類的括號運算子,形式上,該類物件在使用括號時,與一般函式無異,但是普通函式在呼叫結束後,自己所擁有的資料便會回收,只能通過函式傳參和返回值與外界互動,函式物件作為一個物件,可以通過物件的資料成員保持資料的生命週期,更加靈活。
這種定義看似複雜,但是有三大優點
1. Function Object比一般函式更靈巧,因為它可以擁有多個狀態,一個類可以對應多個物件,每個物件在不同的階段可以保持不同的資料,相比於函式傳值,函式物件免去了值傳遞的開銷,靈活而強大;
2. 每個function object都具有其型別,因此你可以將function object型別作為template引數傳遞,然後通過operator()
函式體定義具體行為。這一點相當重要,因為C++標準庫中提供了大量的template引數,函式物件能在這些template中來去自如;
3. 說起來你可能不信,在執行速度上function object比function pointer更快
幾種使用函式物件的場景
下面通過幾個例子來介紹函式物件的用武之地。
Function Object擁有多種狀態
下面展示function object如何行為像個函式,又擁有多個狀態——
#include<iostream>
#include<list>
#include<algorithm>
#include<iterator>
using namespace std;
class IntSequence {
private:
int value;
public:
IntSequence(int initialValue) :value(initialValue) {
}
int operator()() {
return ++value;
}
};
int main(){
list<int> coll;
generate_n(
back_inserter(coll), // 使用back_inserter迭代器插入元素
9, // 插入9個元素
IntSequence(1)); // 插入值由IntSequence產生的臨時物件指定
for (auto elem : coll) {
cout << elem << " ";
}
cout << endl;
generate_n(
back_inserter(coll),
6,
IntSequence(94));
for (auto elem : coll) {
cout << elem << " ";
}
cout << endl;
return 0;
}
這裡,第一次使用generate_n
函式的時候,通過IntSequence(1)
指定了從1開始增加值,在generate_n
執行期間,IntSequence(1)
所產生的臨時物件始終有效,因此產生了2 3 4 5
序列,而在第二個generate_n
執行時,第一個IntSequence(1)
所產生的函式物件已不再有效,產生作用的是IntSequence(94)
。因此,每一次產生臨時物件,其在generate_n
執行期間,保持了內部狀態。如果希望函式物件保持一種外部狀態,即其物件能與外部進行互動,那麼只需要擴充套件物件生命週期到期望的位置即可。
#include<iostream>
#include<list>
#include<algorithm>
#include<iterator>
using namespace std;
class IntSequence {
private:
int value;
public:
IntSequence(int initialValue) :value(initialValue) {
}
int operator()() {
return ++value;
}
};
int main(){
list<int> coll;
// 這裡的generate_n的模板引數不太一樣
generate_n<back_insert_iterator<list<int>>,int,IntSequence&>(
back_inserter(coll),
5,
fo); // 【1】這裡使用的是fo物件的引用
for (auto elem : coll) {
cout << elem << " ";
}
cout << endl;
generate_n(
back_inserter(coll),
5,
IntSequence(94)); // 【2】這裡使用的是臨時物件
for (auto elem : coll) {
cout << elem << " ";
}
cout << endl;
generate_n(
back_inserter(coll),
5,
fo); // 【3】使用fo物件,但是這裡是按值傳遞
for (auto elem : coll) {
cout << elem << " ";
}
cout << endl;
generate_n(
back_inserter(coll),
5,
fo); // 【4】繼續使用fo物件,作為對比
for (auto elem : coll) {
cout << elem << " ";
}
cout << endl;
return 0;
}
從程式執行結果不難看出,我們可以通過引用把函式物件的狀態傳遞出來。
【1】處使用的是引用傳值,因此,generate_n
函式內部無論發生什麼,都會被fo
物件記錄,此所謂外部狀態;
【2】處使用的是IntSequence
產生的臨時物件,因此和fo
物件沒有關係,只和臨時物件的初值有關;
【3】處使用的是fo
物件,但需要注意的是,這裡是按值傳遞(by value),但由於fo物件已經被【1】處的引用更改了 狀態,此時從7
開始插入;
【4】處依舊是按值傳遞,顯而易見,fo
物件還是從7
開始傳值,由此可見,【3】並沒有改變fo
的狀態,同理,【4】也沒有。
for_each()的返回值
for_each演算法有一個獨門絕技,就是可以傳回function object——
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
class MeanValue {
private:
long num;
long sum;
public:
MeanValue() :num(0), sum(0) {};
void operator()(int elem) {
++num;
sum += elem;
}
double value() {
return static_cast<double>(sum) / static_cast<double>(num);
}
};
int main(){
vector<int> coll = { 1,2,3,4,5,6,7,8,9,10 };
MeanValue mv = for_each(coll.begin(), coll.end(),
MeanValue()); //【1】 這裡傳遞的是一個MeanValue的臨時物件,發揮功能的就是operator()
cout << "mean value:" << mv.value() << endl;
return 0;
}
作為關聯容器排序規則
關聯容器都會提供一個定義排序規則的介面,而該介面用函式物件來定義規則,簡直天作之合。一方面,函式物件擁有型別,可以作為一種模板引數傳值;另一方面,函式物件又可以擁有狀態,功能強大。
#include<iostream>
#include<set>
using namespace std;
class Person{
public:
Person(string str1, string str2):firstname(str1),lastname(str2) {}
string firstname;
string lastname;
friend ostream& operator<<(ostream&out, const Person & p);
};
class PersonSortCriterion {
public:
bool operator() (const Person&p1, const Person&p2) const {
return p1.firstname < p2.firstname ||
(p1.firstname == p2.firstname&&p1.lastname < p2.lastname);
}
};
// 為了方便輸出,我們過載了<<符號
ostream &operator<<(ostream&out,const Person & p)
{
// TODO: insert return statement here
cout << p.firstname << " " << p.lastname;
return out;
}
int main(){
set<Person,PersonSortCriterion> ss{
Person("Jason","Lee"),
Person("Jack","Chen"),
Person("Alpha","Lee"),
Person("Alience","Steven"),
Person("Luffy","Lily") };
for (auto&elem : ss) {
cout << elem << endl;
}
return 0;
}