閱讀240 返回首頁    go 阿裏雲 go 技術社區[雲棲]


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++”, 但是一旦熟悉之後,程序員就能準確的編寫一個簡單的,就地的,帶狀態的函數定義。當然了,如果需要全局共享的代碼邏輯,我們還是需要用函數(無狀態)或者仿函數(有狀態)封裝起來。


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

最後更新:2017-04-03 12:53:56

  上一篇:go C++編程規範之32:弄清楚要編寫的是哪種類
  下一篇:go android繪製虛線