`
anzhsoft
  • 浏览: 23277 次
  • 性别: Icon_minigender_1
  • 来自: 天津
文章分类
社区版块
存档分类
最新评论

C++ 仿函数/函数指针/闭包lambda

 
阅读更多

在上一篇文章中介绍了C++11新引入的lambda表达式(C++支持闭包的实现),现在我们看一下lambda的出现对于我们编程习惯的影响,毕竟,C++11历经10年磨砺,出140新feature,对于我们的programming idiom有深远影响。我们先看一下是不是lambda的出现对于仿函数的影响。

1) 仿函数

wikipedia 的定义:

A function object, also called a functor, functional, or functionoid, is a computer programming construct allowing an object to be invoked or called like it was an ordinary function, usually with the same syntax.
Functor/Function Object翻译过来就是仿函数。它是通过重载()运算符模拟函数形为的类。也就是说,它不是函数(所以仿函数翻译的很贴切)。因为它重载了()运算符,因此可以像调用函数一样对它进行调用。STL中大量运用了Function Object,也提供了很多预先定义的Function Object。还是从vector遍历举例:

class PrintInt
{
public:
    void operator()(int elem) const
    {
        std::cout<<elem<<' ';
    }
};

std::vector<int> v;
for_each(v.begin(),v.end(), PrintInt()); 

//C++ 11 lambda stype
for_each(begin(v), end(v), [](int n){ cout<< n <<", "; });

仿函数的优点:

1.仿函数是对象,可以拥有成员函数和成员变量,即仿函数拥有状态(states)
2.每个仿函数都有自己的类型
3.仿函数通常比一般函数快(很多信息编译期确定)

首先简单看一下for_each的源码:

// TEMPLATE FUNCTION for_each
template<class _InIt,
    class _Fn1> inline
    _Fn1 for_each(_InIt _First, _InIt _Last, _Fn1 _Func)
    {    // perform function for each element

    _DEBUG_RANGE(_First, _Last);
    _DEBUG_POINTER(_Func);
    _CHECKED_BASE_TYPE(_InIt) _ChkFirst(_CHECKED_BASE(_First));
    _CHECKED_BASE_TYPE(_InIt) _ChkLast(_CHECKED_BASE(_Last));
    for (; _ChkFirst != _ChkLast; ++_ChkFirst)
        _Func(*_ChkFirst);
    return (_Func);
    }
    
//effective STL 
template< typename InputIterator, typename Function >
Function for_each( InputIterator beg, InputIterator end, Function f ) {
    while ( beg != end )
        f( *beg++ );
}
简单来说, for_each就是一个模板函数,将for循环语句封装起来,前面两个参数都是迭代器,第三个参数是使用一个函数指针(或仿函数),其功能是对每一个迭代器所指向的值调用仿函数。
STL包括了许多不同的预定义的函数对象,包括算子(plus,minus,multiplies,divides,modulus和negate),算术比较(equal_to,not_equal_to,greater,less,greater_equal和less_equal),和逻辑操作(logical_and,logical_or和logical_not)。这样你就可以不用手动写新的函数对象而是用这些函数对象就可以组合出相当复杂的操作。

当然,为了仅仅是遍历vector并且输出,可以使用更简单的方式,我们第一个例子仅仅是为了说明仿函数怎么用。

copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout," "));

其实通过lambda,很多STL的仿函数可以不用。但是既然有轮子了,所以如果了解或者会使用这些仿函数,无疑会更能优美、高效的编写代码。

使用std::negate<int>()进行数组元素求反:

transform( vect.begin(), vect.end(), vect.begin(), std::negate<int>() ); 

// TEMPLATE STRUCT negate 
template<class _Ty>   struct negate         : public unary_function<_Ty, _Ty>    
 { // functor for unary operator-     
   _Ty operator()(const _Ty& _Left) const      
     { // apply operator- to operand       
       return (-_Left);      
     } 
};

将容器中所有小于5的元素删除。

auto iter = std::remove_if( ivec.begin(), ivec.end(), std::bind2nd( std::less<int>(), 5 ) );
ivec.erase( iter, ivec.end() );

关于std::less<int>

// TEMPLATE STRUCT less
template<class _Ty>
    struct less
        : public binary_function<_Ty, _Ty, bool>
    { // functor for operator<
    bool operator()(const _Ty& _Left, const _Ty& _Right) const
        { // apply operator< to operands
        return (_Left < _Right);
        }
    };

关于bind2nd的实现,实现过程也有仿函数的运用:

// TEMPLATE FUNCTION bind2nd
template<class _Fn2,
    class _Ty> inline
    binder2nd<_Fn2> bind2nd(const _Fn2& _Func, const _Ty& _Right)
    { // return a binder2nd functor adapter

    typename _Fn2::second_argument_type _Val(_Right);
    return (std::binder2nd<_Fn2>(_Func, _Val));
    }

// TEMPLATE CLASS binder2nd
template<class _Fn2>
    class binder2nd
        : public unary_function<typename _Fn2::first_argument_type,
            typename _Fn2::result_type>
    { // functor adapter _Func(left, stored)
public:
    typedef unary_function<typename _Fn2::first_argument_type,
        typename _Fn2::result_type> _Base;
    typedef typename _Base::argument_type argument_type;
    typedef typename _Base::result_type result_type;

    binder2nd(const _Fn2& _Func,
        const typename _Fn2::second_argument_type& _Right)
        : op(_Func), value(_Right)
        { // construct from functor and right operand

        }

    result_type operator()(const argument_type& _Left) const
        { // apply functor to operands

        return (op(_Left, value));
        }

    result_type operator()(argument_type& _Left) const
        { // apply functor to operands

        return (op(_Left, value));
        }

protected:
    _Fn2 op; // the functor to apply
    typename _Fn2::second_argument_type value; // the right operand
};


综上所述,由于STL内置的仿函数功能强大,因此如果熟悉的话可以加以利用。否则,使用lambda也是不错的选择,毕竟,都能写出优雅的代码。

如果非要细分的话,lambda什么时候可以替代仿函数?我们需要从lambda的限制说起。这一点很大程度上是由lambda的捕捉列表的限制造成的。在现行C++11的标准中,捕捉列表仅能捕捉父作用域的自动变量。比如下面这个例子:

 int d = 0;
 void test()
 {
    auto ill_lambda = [d]{};
 }
g++会编译通过,但是会有warning。而一些严格遵照C++11标准的编译器则会直接报错。仿函数则没有次限制。

简单总结一下,使用lambda替代仿函数应该满足一下几个条件:

a) 是局限于一个局部作用域中使用的代码逻辑。

b) 这些代码逻辑需要作为参数进行传递。

lambda被设计的主要目的之一就是简化仿函数的使用,虽然语法看起来不像是“典型的C++”, 但是一旦熟悉之后,程序员就能准确的编写一个简单的,就地的,带状态的函数定义。当然了,如果需要全局共享的代码逻辑,我们还是需要用函数(无状态)或者仿函数(有状态)封装起来。


引用:
http://blog.chinaunix.net/uid-20384806-id-1954378.html

分享到:
评论

相关推荐

    C++-lambda闭包函数.pdf

    c++ C++_lambda闭包函数.pdf

    JS匿名函数、闭包

    但是,当函数返回了一个闭包时,这个函数的作用域将会一直在内存中保存到闭包不存在为止;使用闭包可以在JavaScript中模仿块级作用域(JavaScript本身没有块级作用域的概念),要点如下: 创建并立即调用一个函数...

    JavaScript闭包函数

    闭包是ECMAScript (JavaScript)最强大的特性之一,但用好闭包的前提是必须理解闭包。闭包的创建相对容易,人们甚至会在不经意间创建闭包,但这些无意创建的闭包却存在潜在的危害,尤其是在比较常见的浏览器环境下...

    js闭包函数

    学习javascript闭包函数的实用文档,讲解全面、详细。pdf格式,

    数据库求属性集闭包&函数依赖闭包

    4) 计算函数依赖的闭包。此步骤不作要求,但要会方法。个人总结:将所有属性元素组成一个集合(域)记为R;求R的所有子集(要用到第二步中的全排列~~~),设其中一个为Ri;对每一个子集求其闭包,记为Ri+;然后求Ri...

    c++求传递闭包

    传递闭包C++描述,应用c++语言描述传递闭包算法

    匿名函数与闭包函数.php

    匿名函数与闭包函数.php

    python 函数 函数的偏函数 高阶函数 返回函数 匿名函数 闭包.md

    ​ 当我们写一个参数比较多的函数时,如果有些参数,大部分场景下都是某一个固定值,那么为了简化使用,就可以创建一个新函数,指定我们要使用的函数的某个参数,为某个固定的值;这个新函数就是“偏函数” 2. ...

    swift 闭包+嵌套函数+extension+单例+嵌套函数

    swift 闭包+嵌套函数+extension+单例+嵌套函数

    PHP闭包函数详解

    匿名函数也叫闭包函数(closures允许创建一个没有指定没成的函数,最经常用作回调函数参数的值。 闭包函数没有函数名称,直接在function()传入变量即可 使用时将定义的变量当作函数来处理 $cl = function($name){ ...

    求闭包 属性集闭包 函数依赖的闭包

    1. 由用户输入函数依赖,当用户输入End时,表示所有依赖都输入完毕。(即函数依赖是由用户自己定的,程序中不能假定某个具体的依赖)。 2. 函数依赖的形式是ABC, ABE这样的形式,为了简单起见,我们假定所有的属性...

    C++ 11,14,17中的 Lambda 表达式 _ Microsoft Docs[2].pdf

    查Lambda表达式资料时很容易被函数闭包、Lambda演算、形式系统这些深奥名词淹没而放弃学习,其实Lambda表达式就是匿名函数(annoymous function)——允许我们使用一个函数,但不需要给这个函数起名字。还是有点难懂...

    Java闭包 Java闭包

    在2013年发布的 JavaSE8 中包含一个叫做 ...闭包允许我 们创建函数指针,并把它们作为参数传递。在这篇文章中,将粗略的看一遍Java8的特性,并介绍 Lambda表达式。而且将试着放一些样例程序来解释一些概念和语法。

    swift_函数和闭包

    函数和闭包

    python中的闭包函数

    闭包函数初探 通常我们定义函数都是这样定义的 def foo(): pass 其实在函数式编程中,函数里面还可以嵌套函数,如下面这样 def foo(): print(hello world in foo) def bar(): print(hello world in bar) 此时...

    C++对称闭包对称闭包

    用矩阵求对称闭包。简洁,轻松学习,相互交流。

    JS 第16章 匿名函数和闭包

    JS中的匿名函数和闭包,详细解释了大家的疑惑,提升大家对的JS的进一步的理解!

    PHP闭包函数传参及使用外部变量的方法

    在Laravel控制器写两个方法,一个是在内部创建一个闭包函数,一个是执行传过来的闭包函数,测试闭包的写法,use使用外部变量,及闭包函数的传参。如下: //测试闭包传参及use使用外部变量 public function ...

    js高级函数之闭包

    js高级中的函数之闭包函数全解与应用场景(循环闭包,定时器,面试题)

    Dart中的函数 函数的定义 可选参数 默认参数 命名参数 箭头函数 匿名函数 闭包等.zip

    本资源讲解了:Dart中的函数 函数的定义 可选参数 默认参数 命名参数 箭头函数 匿名函数 闭包等,含PPT和源代码

Global site tag (gtag.js) - Google Analytics