如果你經常使用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