你注意到C++的函數對象都是傳值的形式了嗎?—boost::ref的強大用處~

來源:互聯網
上載者:User

 

如果你經常使用STL演算法,那麼你會注意到函數對象的傳遞都是傳值的形式如下面的sort, for_each,_Compare __comp 而不是 _Compare& __comp傳遞引用。

template<typename _RandomAccessIterator, typename _Compare>    inline void    sort(_RandomAccessIterator __first, _RandomAccessIterator __last,     _Compare __comp)    {    }
template<typename _InputIterator, typename _Function>    _Function    for_each(_InputIterator __first, _InputIterator __last, _Function __f)    {      // concept requirements      __glibcxx_function_requires(_InputIteratorConcept<_InputIterator>)      __glibcxx_requires_valid_range(__first, __last);      for ( ; __first != __last; ++__first)    __f(*__first);      return __f;    }

這裡你是否會疑惑呢,為什麼要用傳值的形式而不用傳遞引用呢,畢竟函數對象區別於函數指標的一大優點是自身可以帶有狀態變數。如下面這個函數對象它是帶有一個狀態變數a的,其實可以帶有更複雜更占記憶體的變數更多的變數,那麼傳值的方式肯定有很大的拷貝代價了。

struct Func{    int a;    Func()    : a(0)    {    }    void operator()(int x)    {        add(x);    }    void add(int x)    {        cout << "a: " << a << endl;        cout << "this: " << this << endl;        cout << "add " << x << endl;        a += x;    }};
另外有一點是用傳引用的方式能夠改變狀態變數如下面的a的值,如果我們希望傳遞的方式就是引用方式呢,我們就是希望被傳遞的Func()能能夠在傳遞給其它函數後被其它函數使用而且改變自身狀態,其它函數使用之後它的自身狀態希望是變化的。
例如我寫過一個對於OTL讀取資料庫的封裝。
template<typename Func>    void process(Func func)    {        //        process(func);        LOG(INFO) << Pval(sql) << "\n";        try        {            otl_stream os(1024, sql.c_str(), conn); //串連好資料庫,並讀出資料            func(os);            os.flush();        }        catch (otl_exception& p)        { // intercept OTL exceptions            cerr << p.msg << endl; // print out error message            cerr << p.stm_text << endl; // print out SQL that caused the error            cerr << p.sqlstate << endl; // print out SQLSTATE message            cerr << p.var_info << endl; // print out the variable that caused the error        }    }

現在我有一個應用,讀取資料庫中的詞並統計熱門詞,

struct ReadFunc{    typedef std::deque<Node> Deque;    typedef std::priority_queue<Node, Deque, std::greater<Node> > PQueue;    typedef std::tr1::unordered_map<string, int> HashMap;    HashMap hash_map;    PQueue m_pqueue;    ch_convert::ChConverter m_converter;    long long m_keyNum;    long long m_keyCount;    template<typename Stream>            void operator()(Stream & os)    {        string key;        int count;        int line_num = 0;        long long key_count = 0;        boost::array<char, 2 > seperator = {' ', '\t'};        while (!os.eof())        {            os >> key >> count;           …         }     }

現在問題來了,見下面

void run()    { //normal 情況 熱門詞統計        LOG(INFO) << "Normal run" << endl;        ReadFunc reader;        run_(reader);  //在函數裡面m_dbreader.process(reader),這裡我就希望reader中的記錄熱門詞的m_queue就是要被改變!如果傳值。。。oh my god!!!!        finalize_(reader);    }

那麼言歸正傳,為什麼STL要是使用傳值的方式呢??用於傳遞函數對象呢??? 我覺得是為了下面的原因

std::sort(begin, end, Cmp());

看到了嗎,傳遞臨時的函數對象,這個很常見!! 我只要一行代碼就OK。如果是傳引用的形式呢_Compare& __comp,那抱歉,你不能這麼寫,因為臨時對象不能傳引用。。。

你要寫成

Cmp cmp;

std::sort(begin, end, cmp);

很麻煩是吧 呵呵,我還是希望傳引用有辦法兩全其美嗎?

有一個work around, 用const 引用傳遞,這樣可以用來傳遞臨時變數了。

template<typename _RandomAccessIterator, typename _Compare> inline void sort(_RandomAccessIterator __first, _RandomAccessIterator __last, const _Compare& __comp) { }

這個辦法對於我上面提到的統計熱門詞也是試用的,我可以傳遞引用,並且改變我的reader內部狀態了,但是編譯器有些不高興了,它會warning因為你是const的聲明,你強制去掉了它的const呵呵。。。。

C++標準鼓勵我們怎麼做呢,比較for_each等介面都是按照傳值寫的啊。。。 你如果用那些介面你就沒辦法傳引用啦?

用boost::ref, kaka 問題全部解決。

template<typename T>void sort(T func){    func(3);}Func func;func(1);cout << "func.a: " << func.a << endl;   //1sort(boost::bind<void>(boost::ref(func), _1));cout << "func.a: " << func.a << endl;  //4    //狀態改變了,我們傳遞的是引用~
 
 注意C++標準的tr1也實現了,ref,bind,function等等但是我還沒完全弄懂,似乎有問題通不過編譯,所以當前還是建議暫時用boost的靠譜一些。。。 呵呵
 
附註關於bind
 

http://aszt.inf.elte.hu/~gsd/halado_cpp/ch11.html

boost::bind is a generalization of the standard functions std::bind1st and std::bind2nd. It supports arbitrary function objects, functions, function pointers, and member function pointers, and is able to bind any argument to a specific value or route input arguments into arbitrary positions. Bind does not place any requirements on the function object; in particular, it does not need the result_type, first_argument_type and second_argument_type standard typedefs.

Example:

int f(int a, int b)
{
    return a + b;
}

int g(int a, int b, int c)
{
    return a + b + c;
}

Usage of bind

bind(f, 1, 2) will produce a "nullary" function object that takes no arguments and returns f(1, 2). Similarly, bind(g, 1, 2, 3)() is equivalent to g(1, 2, 3).

It is possible to selectively bind only some of the arguments.

bind(f, _1, 5)(x)  /* is equivalent to */  f(x, 5)

Here _1 is a placeholder argument that means "substitute with the first input argument."

The same with the older version:

std::bind2nd(std::ptr_fun(f), 5)(x);

More complex solutions:

bind(f, _2, _1)(x, y);                 // f(y, x)
bind(g, _1, 9, _1)(x);                 // g(x, 9, x)
bind(g, _3, _3, _3)(x, y, z);          // g(z, z, z)
bind(g, _1, _1, _1)(x, y, z);          // g(x, x, x)

Note that, in the last example, the function object produced by bind(g, _1, _1, _1) does not contain references to any arguments beyond the first, but it can still be used with more than one argument. Any extra arguments are silently ignored, just like the first and the second argument are ignored in the third example.

The argumenst are copies. If we want to use references, we need helper functions:

int i = 5;

bind(f, i, _1);
bind(f, ref(i), _1);

Function objects

The bind is not limited to functions; it accepts arbitrary function objects. In the general case, the return type of the generated function object's operator() has to be specified explicitly (without a typeof operator the return type cannot be inferred):

struct F
{
    int operator()(int a, int b) { return a - b; }
    bool operator()(long a, long b) { return a == b; }
};

F f;

int x = 104;
bind<int>(f, _1, _1)(x);   // f(x, x), i.e. zero 

int x = 8;
bind(std::less<int>(), _1, 9)(x);    // x < 9

Example

class image;

class animation
{
public:

    void advance(int ms);
    bool inactive() const;
    void render(image & target) const;
};

std::vector<animation> anims;

template<class C, class P> void erase_if(C & c, P pred)
{
    c.erase(std::remove_if(c.begin(), c.end(), pred), c.end());
}

void update(int ms)
{
    std::for_each(anims.begin(), anims.end(), boost::bind(&animation::advance, _1, ms));
    erase_if(anims, boost::mem_fn(&animation::inactive));
}

void render(image & target)
{
    std::for_each(anims.begin(), anims.end(), boost::bind(&animation::render, _1, boost::ref(target)));
}

Reference wrapper

The header boost/ref.hpp defines the class template boost::reference_wrapper<T>, the two functions boost::ref and boost::cref that return instances of boost::reference_wrapper<T>, and the two traits classes boost::is_reference_wrapper<T> and boost::unwrap_reference<T>.

The purpose of boost::reference_wrapper is to contain a reference to an object of type T. It is primarily used to "feed" references to function templates (algorithms) that take their parameter by value.

To support this usage, boost::reference_wrapper provides an implicit conversion to T &. This usually allows the function templates to work on references unmodified.

namespace boost
{
    template<class T> class reference_wrapper;
    template<class T> reference_wrapper<T> ref(T & t);
    template<class T> reference_wrapper<T const> cref(T const & t);
    template<class T> class is_reference_wrapper<T const>;
    template<class T> class unwrap_reference<T const>;
}

Implementation is trivial

template<class T> class reference_wrapper
{
public:
    typedef T type;
    explicit reference_wrapper(T & t);

    operator T & () const;

    T & get() const;
    T* get_pointer() const;
private:
    T* t_;
};

Prev     Up     Next
Define your own streambuffer     Home     Tuple

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.