1. 程式人生 > >C++STL中的函式物件

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

在這裡插入圖片描述