函数对象
函数对象是行为类似函数的对象,是一个定义了operator()的对象,是泛型编程强大威力和纯粹抽象概念的又一个例证。
一个函数对象
任何行东西,其行为像函数,它就是个函数。所谓函数行为是指可以“使用小括号传递实参,借以调用某个东西”,即通过小括号的运用和实参的传递实现调用。要实现这种可能,只需要定义operator(),并给予合适的参数类型,如下所示:
class X{
public:
// define "function call" operator:
return value operator()(arguments) const;
...
}
随即便可以把X的对象当作函数来调用了,完整的示例如下所示:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class PrintInt
{
public:
void operator()(int elem) const
{
cout << elem << " ";
}
};
int main()
{
vector<int> coll;
for (int i = 0; i<=9; ++i)
{
coll.push_back(i);
}
for_each(coll.cbegin(), coll.cend(), PrintInt());
cout << endl;
system("pause");
return 0;
}
函数对象相对于寻常函数有以下优点:
-
函数对象是一种带状态的函数。
函数对象可拥有成员函数和成员变量,这意味着函数对象拥有状态。事实上,在同一时间点,相同类型的两个不同函数对象所表述的相同机能,可能具备不同的状态。这在寻常函数是不可能的。另一个好处是,可以在运行期初始化它们。
-
每个函数对象都有其自己的类型。
寻常函数,唯有在其签名式不同时才算类型不同。而函数签名即使签名式相同,也可以有不同的类型。由函数对象定义的每一个函数行为都有其自己的类型。这可以将函数行为当作template参数来运用。另外还有个好处:容器类型也会因为函数对象不同而不同。
-
函数对象通常比寻常函数速度快。
函数对象拥有内部状态
函数对象表现得像个函数又同时拥有状态,如下例所示。
#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), 9, IntSequence(1));
for (int i : coll)
{
cout << i << "\t";
}
cout << endl;
generate(next(coll.begin()), prev(coll.end()), IntSequence(42));
for (int i : coll)
{
cout << i << "\t";
}
cout << endl;
return 0;
}
在默认情况下函数对象是以值传递的方式传递给算法的,这样算法运行后就不会改变函数对象的状态,这样的好处是可以传递常量表达式或暂态表达式;而缺点则是函数对象的状态无法被算法改变。
IntSequence seq(1);
//insert sequence beginning with 1
generate_n(back_inserter(coll), 9, seq);
//insert sequence beginning with 1 again
//seq do not change
generate_n(back_inserter(coll), 9, seq);
有三个办法可以从使用函数对象的算法中得到结果反馈:
1.在外部持有状态,并让函数对象指向它;
2.以引用的方式传递函数对象;
3.利用for_each()算法的返回值。
为了以引用方式传递函数对象,在调用算法时必须明确标识该函数对象是一个引用,即将算法中template实参明白标示就可以了,如下所示:
IntSequence seq(1);
generate_n<back_insert_iterator<list<int>>, int, IntSequence&>(back_inserter(coll), 9, seq);
此外,在使用for_each()算法时,不需要以引用的方式传递函数对象来获取返回值,因为for_each()算法可以将其使用的函数对象传回来,可以通过其返回值来获取函数对象的状态。
#include <iostream>
#include <algorithm>
#include <vector>
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> col = {1, 2, 3, 4, 5, 6, 7, 8 };
MeanValue mv = for_each(col.begin(), col.end(), MeanValue());
cout << "mean value:" << mv.value() << endl;
system("pause");
return 0;
}
C++中预定义的函数对象
C++标准库内含若干预定义的函数对象,涵盖了许多基础运算。一个典型的例子是作为排序准则的函数对象。Operator <的默认排序准则乃是调用less<>。使用预定义的函数对象的示例如下,其中negate函数对象的作用是将传入的int设为负,multiplies函数对象为乘法运算。
#include <iostream>
#include <deque>
#include <functional>
#include <algorithm>
using namespace std;
int main()
{
deque<int> coll = {1, 2, 3, 5, 7, 11, 3, 17, 19};
cout << "initialized:" << endl;
for (auto elem : coll)
{
cout << elem << " ";
}
cout << endl;
transform(coll.cbegin(), coll.cend(), coll.begin(), negate<int>());
cout << "negated:" << endl;
for (auto elem : coll)
{
cout << elem << " ";
}
cout << endl;
transform(coll.cbegin(), coll.cend(),coll.cbegin(), coll.begin(), multiplies<int>());
cout << "multiplies:" << endl;
for (auto elem : coll)
{
cout << elem << " ";
}
cout << endl;
system("pause");
return 0;
}
函数对象的Binder
可以使用特殊的functional adapter(函数适配器)或所谓的binder,将预定义的函数对象和其他数值结合为一体,示例如下:
#include <set>
#include <deque>
#include <functional>
#include <algorithm>
#include <iterator>
#include <iostream>
using namespace std;
using namespace std::placeholders;
int main()
{
set<int, greater<int>> coll1 = {1, 2, 3, 4, 5, 6 ,7, 8 ,9};
deque<int> coll2;
cout << "coll1 initialized:" << endl;
for (auto elem : coll1)
{
cout << elem << " ";
}
cout << endl;
transform(coll1.cbegin(), coll1.cend(), back_inserter(coll2), bind(multiplies<int>(), _1, 10));
cout << "coll2 transformed:" << endl;
for (auto elem : coll2)
{
cout << elem << " ";
}
cout << endl;
replace_if(coll2.begin(), coll2.end(), bind(equal_to<int>(), _1, 70), 42);
cout << "coll2 replaced:" << endl;
for (auto elem : coll2)
{
cout << elem << " ";
}
cout << endl;
coll2.erase(remove_if(coll2.begin(), coll2.end(), bind(logical_and<bool>(), bind(greater<int>(), _1, 50), bind(less<int>(), _1, 80))), coll2.end());
cout << "coll2 removed:" << endl;
for (auto elem : coll2)
{
cout << elem << " ";
}
cout << endl;
system("pause");
return 0;
}
bind()允许借由低层的函数对象和占位符合成高层的函数对象。占位符即是“带有前缀下划线”的数值标识符。占位符有自己的命名空间:std::placeholders。通过指定
bind(multiplies<int>(), _1, 10);
便定义出一个函数对象,会将传入的第一个实参乘以10。也可以使用这样一个函数对象,将任何数值乘以10。
auto f = bind(multiplies<int>(), _1, 10);
cout<<f(99)<<endl;
另外还有一种定义函数对象的方法。例如,想要“针对集合内的每个元素”调用某成员函数,可以这么做:
for_each(coll.cbegin(), coll.cend(), bind(&Person::save, _1));
函数对象bind会绑定一个所给的成员函数,后者会因每个元素而被调用。