應用器與操縱器
操縱器:以某種方式作用于他的參數所表示的數據.
應用器: 重載一個運算符,他的操作數是一個可操作的值和一個作用于這個值的操縱器.
問題
在C++中我們可以通過重載operator<<
來將指定的類型對象輸出到輸出流中,例如:std::cout << value;
我們通過觀察STL提供的std::endl
發(fā)現,他可以將輸出緩沖區(qū)清空.即:std::cout << std::endl;
將會把cout
的輸出緩沖期清空,并且完成換行.假設我們也有一個清空緩沖區(qū)的函數flush
,他將接收一個參數ostream&
類型,并且返回一樣的類型,則他的實現應該是:
ostream& flush(ostream& file) {
// do something
return file;
}
這樣我們可以用這個flush
函數來部分替代std::endl
.但是我們還不能寫std::cout << flush;
只能flush(std::cout);
如何才能按照std::endl
的方式書寫呢?
一種解決方案
由于flush
只是一個函數,他不能重載operator<<
,所以我們就搞一個類型,然后讓這個類型重載operator<<
.
ostream& flush(ostream& file) {
// do something
return file;
}
class FLUSHTYPE{};
FLUSHTYPE FULSH;
ostream& operator<<(ostream& o, FLUSHTYPE f) {
return flush(o);
}
現在我們可以使用FLUSH
這個對象進行
cout << x << FLUSH << y << FLUSH << z << FLUSH;
的操作了.
但是上面的解決方案僅僅是為了重載operator<<
而引入了一個新的類型,而且我們還必須定義一個這個類型的對象才行.
讓我們來看看使用函數指針能給我們帶來什么方便的地方.
另一種解決方案
我們舍棄掉上面的FLUSHTYPE
類型的定義,而是直接通過函數指針類型重載operator<<
.
ostream& operator<<(ostream& o, ostream& (*f)(ostream&)) {
return (*f)(o);
}
由于flush
函數是符合條件的參數,所以cout << flush;
上面的例子中flush
就是操縱器,operator<<
則是應用器.
多個參數
通過上面的例子我們發(fā)現操縱器和應用器看起來是很有用的工具.那么我們能不能把這兩個概念擴充到所有的函數上呢?
假設我們希望提供一個函數,對于給定的數字可以轉換為人可以讀懂的16進制值.在打印的時候我們希望cout << to_hex(n);
就可以了.
首先我們需要考慮to_hex(n)
的返回值類型應該是什么?如果我們不想依賴任何string類庫的情況下,我們應該返回的是一個char*
.那么我們什么時候釋放掉內存呢?
參考下面的方案:
- 先定義一個函數對象,這個函數對象重載
operator<<
.
class long_fn_obj {
public:
long_fn_obj(ostream& (*f)(ostream&, long ) , long value): func(f), n(value) {}
ostream& operator<<(ostream& 0) {
return (*func)(o, n);
}
private:
ostream& (*func)(ostream& , long);
long n;
};
- 定義long_fn_obj需要的第一個參數的函數:
ostream& hexconv(ostream& o, long n) {
return o << to_hex(n);
}
這里因為我們一旦執(zhí)行過to_hex(n)以后馬上就輸出到了ostream中,所以不存在內存分配釋放的問題.
- 重載hexconv函數
long_fn_obj hexconv(long n){
retrun long_fn_obj( (ostream& (*)(ostream&, long )) hexconv, n); // 這里強制類型轉換,使用第一個版本的重載.
}
現在則可以使用hexconv了:
cout << hexconv(m) << " " << hexconv(n);
簡化
我們將上面實現的hexconv進行泛型化.
- 首先實現函數對象的模板類
template <class stype, class vtype>
class func_obj {
public:
func_obj(stype& (*f)(stype&, vtype), vtype v):fn(f), val(v) {}
stype& operator() (stype& o) {
return (*fn)(o, val);
}
private:
stype& (*fn)(stype&, vtype);
vtype val;
};
- 應用器模板:
template <class stype, class vtype>
stype& operator<<(stype& o, const func_obj<stype, vtype>& im) {
return im(o);
}
- 重寫hexconv
func_obj<ostream, long> hexconv(long n) {
ostream& (*f)(ostream&, long) = hexconv; // 這里的hexconv是前面重載版本的第一個.
return func_obj<ostream, long>(f, n);
}
使用方式沒有任何改變.
思考
如果采用C++11 的Functional提供的工具,我們是不是還可以擴展到任意多參數的場景?
采用template <class stype, class ...ArgsType>
進行擴展.
func_obj
的成員將變?yōu)?/p>
template <class stype, class ...ArgsType>
class func_obj {
private:
std::function<stype&(*)(stype&, ArgsType...)> f;
typedef std::tuple<typename std::decay<ArgsType>::type...> _Td;
_Td args;
};
構造函數和operator()
的實現:
template <class stype, class ...ArgsType>
class func_obj {
public:
func_obj(stype& (*func)(stype&, ArgsType...), ArgsType... arg): f(func), args(arg...) {}
stype& operator() (stype& o) {
return f(o, std::forward(args));
}
private:
std::function<stype&(*)(stype&, ArgsType...)> f;
typedef std::tuple<typename std::decay<ArgsType>::type...> _Td;
_Td args;
};
llvm實現的std::endl
最后我們來看下標準庫使用操縱器和應用器實現的std::endl
// 應用器:這個是定義在類basic_ostream中
inline _LIBCPP_HIDE_FROM_ABI_AFTER_V1
basic_ostream& operator<<(basic_ostream& (*__pf)(basic_ostream&))
{ return __pf(*this); }
// 操縱器
template <class _CharT, class _Traits>
inline _LIBCPP_INLINE_VISIBILITY
basic_ostream<_CharT, _Traits>&
endl(basic_ostream<_CharT, _Traits>& __os)
{
__os.put(__os.widen('\n'));
__os.flush();
}
可以發(fā)現他的實現跟我們的實現基本上是一致的.