C++11新特性之八——函数对象function

本文详细介绍了C++中的函数对象及其优势,并深入探讨了std::function的使用方法,包括其构造、赋值及如何包装普通函数、函数对象和类的成员函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

详细请看《C++ Primer plus》(第六版中文版)

http://www.cnblogs.com/lvpengms/archive/2011/02/21/1960078.html

备注:

函数对象
尽管函数指针被广泛用于实现函数回调,但C++还提供了一个重要的实现回调函数的方法,那就是函数对象。函数对象(也称“函数符”)是重载了“()”操作符的普通类对象。因此从语法上讲,函数对象与普通的函数行为类似。
用函数对象代替函数指针有几个优点:

  首先,因为对象可以在内部修改而不用改动外部接口,因此设计更灵活,更富有弹性。函数对象也具备有存储先前调用结果的数据成员。在使用普通函数时需要将先前调用的结果存储在全程或者本地静态变量中,但是全程或者本地静态变量有某些我们不愿意看到的缺陷。

  其次,在函数对象中编译器能实现内联调用,从而更进一步增强了性能。这在函数指针中几乎是不可能实现的。

  C++11还提供了limbda表达式来实现函数的灵活调用。详见《C++ Primer Plus》第18章。

----------------------------------------------------------------------------------------------------------------------------------------------------------

【以下转自】http://www.cnblogs.com/hujian/archive/2012/12/07/2807605.html

 function是一组函数对象包装类的模板,实现了一个泛型的回调机制。function与函数指针比较相似,优点在于它允许用户在目标的实现上拥有更大的弹性,即目标既可以是普通函数,也可以是函数对象和类的成员函数,而且可以给函数添加状态。
声明一个function时,需要给出所包装的函数对象的返回值类型和各个参数的类型。比如,声明一个function,它返回一个bool类型并接受一个int类型和一个float类型的参数,可以像下面这样:
function<bool (int, float)> f;

下面简要介绍一下function的比较重要的几个接口。

function();
缺省构造函数,创建一个空的函数对象。如果一个空的function被调用,将会抛出一个类型为bad_function_call的异常。
template <typename F> function(F g);

这个泛型的构造函数接受一个兼容的函数对象,即这样一个函数或函数对象,它的返回类型与被构造的function的返回类型或者一样,或者可以隐式转换,并且它的参数也要与被构造的function的参数类型或者一样,或者可以隐式转换。注意,也可以使用另外一个function实例来进行构造。这样做,并且function g为空,则被构造的function也为空。使用空的函数指针和空的成员函数指针也会产生空的function。如果这样做,并且function g为空,则被构造的function也为空。使用空的函数指针和空的成员函数指针也会产生空的function。

template <typename F> function(reference_wrapper<F> g);
这个构造函数与前一个类似,但它接受的函数对象包装在一个reference_wrapper中,用以避免通过值来传递而产生函数或函数对象的一份拷贝。这同样要求函数对象兼容于function的签名。
function& operator=(const function& g);
赋值操作符保存g中的函数或函数对象的一份拷贝;如果g为空,被赋值的函数也将为空。
template<typename F> function& operator=(F g);
这个泛型赋值操作符接受一个兼容的函数指针或函数对象。注意,也可以用另一个 function 实例(带有不同但兼容的签名)来赋值。这同样意味着,如果g是另一个function实例且为空,则赋值后的函数也为空。赋值一个空的函数指针或空的成员函数指针也会使function为空。
bool empty() const;
这个成员函数返回一个布尔值,表示该function是否含有一个函数或函数对象。如果有一个目标函数或函数对象可被调用,它返回 false 。因为一个function可以在一个布尔上下文中测试,或者与0进行比较,因此这个成员函数可能会在未来版本的库中被取消,你应该避免使用它。
void clear();
这个成员函数清除 function, 即它不再关联到一个函数或函数对象。如果function已经是空的,这个调用没有影响。在调用后,function肯定为空。令一个function为空的首选方法是赋0给它;clear 可能在未来版本的库中被取消。
result_type operator()(Arg1 a1, Arg2 a2, ..., ArgN aN) const;
调用操作符是调用function的方法。你不能调用一个空的 function ,那样会抛出一个bad_function_call的异常。调用操作符的执行会调用function中的函数或函数对象,并返回它的结果。
下面分别给出使用function来包装普通函数,函数对象和类的成员函数的参考代码。
1、普通函数
1 int Add(int x, int y)
2 
3 {
4             return x+y;
5 }
6 function<int (int,int)> f = Add;
7 int z = f(2, 3);

2、函数对象

 1 class CStudent
 2 {
 3 public:
 4             void operator() (string strName, int nAge)
 5             {
 6                 cout << strName << " : " << nAge << endl; 
 7             }
 8 };
 9 
10 CStudent stu;
11 function<void (string, int)> f = stu;
12 f("Mike",  12);

3、类的成员函数

 1 struct TAdd
 2 {
 3     int Add(int x,int y)
 4     {
 5         return x+y;
 6     }
 7 };
 8 
 9 function<int  (TAdd *, int, int)> f = TAdd::Add;
10 TAdd tAdd;
11 f(&tAdd, 2, 3);   // 如果前面的模板参数为传值或引用,直接传入tAdd即可

接下来我们来看看使用function来保存函数对象状态的情况。考虑下面的代码:

 1 class CAdd
 2 {
 3 public:
 4     CAdd():m_nSum(0) { NULL; }
 5     int operator()(int i)  //重载 () 运算符
 6     {
 7           m_nSum += i;
 8           return m_nSum;
 9     }
10 
11     int Sum() const 
12     {
13         return m_nSum;
14     }
15 
16 private:
17     int m_nSum;
18 };
19 
20 int main() 
21 {
22     CAdd add;
23     function<int (int)> f1 = add;
24     function<int (int)> f2 = add;
25     cout << f1(10) << "," << f2(10) << "," << add.Sum() << endl;
26     return 0;
27 }
可能和大家想象的结果不一样,上面程序的输出是:10,10,0。我们将同一个函数对象赋值给了两个function,然后分别调用了这两个function,但函数对象中m_nSum的状态并没有被保持,问题出在哪儿呢?这是因为function的缺省行为是拷贝一份传递给它的函数对象,于是f1和f2中保存的都是add对象的拷贝,调用f1和f2后,add对象中的值并没有被修改。

C++ 11中提供了ref和cref函数,来提供对象的引用和常引用的包装。要使function能够正确地保存函数对象的状态,我们可以这样来修改代码:

1 CAdd add;
2 function<int(int)> f1 = ref(add);
3 function<int(int)> f2 = ref(add);
另外,在两个function之间赋值时,如果源function保存的是函数对象的拷贝,则目标function保存的也是函数对象的拷贝;如果源function保存的是函数对象的引用,则目标function保存的也是函数对象的引用。

转载于:https://www.cnblogs.com/yyxt/p/3987717.html

### C++11 Lambda 表达式与函数对象的区别和使用场景 #### 一、Lambda 表达式的特性 Lambda 表达式是 C++11 中引入的一种简洁方式,用于创建匿名函数。它的主要优势在于能够简化代码并提高可读性。通过捕获上下文中的变量,Lambda 可以方便地访问局部作用域内的数据[^1]。 以下是 Lambda 表达式的一个典型例子: ```cpp #include <iostream> int main() { auto add = [](int a, int b) -> int { return a + b; }; std::cout << "Result: " << add(3, 4) << std::endl; return 0; } ``` Lambda 的语法结构通常由以下几个部分组成: - **捕获列表**:指定哪些外部变量被 Lambda 捕获以及如何捕获(值传递还是引用传递)。 - **参数列表**:类似于普通函数的参数声明。 - **返回类型**(可选):如果无法自动推导,则需显式指定。 - **函数体**:执行逻辑所在的部分。 #### 二、函数对象的特点 函数对象是指可以通过 `operator()` 调用的对象。它们本质上是一个类实例,在该类中实现了重载的括号运算符[]。相比于普通的函数指针或 Lambda 表达式,函数对象提供了更大的灵活性,允许封装状态和其他成员变量。 下面展示了一个简单的函数对象定义及其应用: ```cpp #include <iostream> struct Adder { int base_value; explicit Adder(int value) : base_value(value) {} int operator()(int increment) const { return base_value + increment; } }; int main() { Adder my_adder{5}; std::cout << "Add Result: " << my_adder(7) << std::endl; return 0; } ``` 在这个例子中,`Adder` 类不仅包含了加法功能还保存了初始值作为内部状态的一部分。 #### 三、两者的主要区别 | 特性 | Lambda 表达式 | 函数对象 | |---------------------|--------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------| | 定义形式 | 内联定义,无需额外命名 | 需要单独定义一个类 | | 状态管理 | 支持通过捕获机制获取外部变量 | 自然支持复杂的状态管理和多态行为 | | 性能 | 编译器优化良好,运行效率高 | 同样高效,但在某些情况下可能涉及虚表开销 | | 复杂度 | 更适合轻量级任务 | 对于需要维护大量状态或者继承关系的任务更加适用 | 尽管 Lambda 是一种强大的工具,但它仍然只是函数对象的一种特殊表现形式——即无名版本。因此当面对较为复杂的业务需求时,传统意义上的函数对象往往更具优势。 #### 四、实际应用场景对比分析 对于一些小型回调函数来说,采用 Lambda 显得更为直观便捷;而涉及到长期存在的算法组件或者是那些需要频繁修改其属性的情况,则应该优先考虑设计专门的函数对象[^2]^。 例如标准库容器配合 STL 算法时经常需要用到自定义比较规则的情形下,两种方案均可胜任但各有侧重: ##### 使用 Lambda 实现快速排序定制化比较规则 ```cpp std::vector<int> numbers = {5, 8, 2, 9, 1}; std::sort(numbers.begin(), numbers.end(), [](const int& lhs, const int& rhs){return lhs > rhs;}); // 结果按照降序排列 ``` ##### 利用 Function Object 达成相同目的的同时具备扩展能力 ```cpp class DescendingComparator{ public: bool operator () (const int &lhs,const int &rhs)const{return lhs>rhs;} }; std::vector<int> nums={5,8,2,9,1}; std::sort(nums.begin(),nums.end(),DescendingComparator()); // 这里也可以很容易加入更多字段参与比较等高级操作 ``` 综上所述,虽然二者都能满足大多数编程需求,但是在特定条件下选择合适的技术手段才能更好地发挥各自特长[^3].
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值