應用器、操縱器和函數對象

應用器與操縱器

操縱器:以某種方式作用于他的參數所表示的數據.
應用器: 重載一個運算符,他的操作數是一個可操作的值和一個作用于這個值的操縱器.

問題

在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*.那么我們什么時候釋放掉內存呢?
參考下面的方案:

  1. 先定義一個函數對象,這個函數對象重載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;
};
  1. 定義long_fn_obj需要的第一個參數的函數:
ostream& hexconv(ostream& o,  long n) {
    return o << to_hex(n);
}

這里因為我們一旦執(zhí)行過to_hex(n)以后馬上就輸出到了ostream中,所以不存在內存分配釋放的問題.

  1. 重載hexconv函數
long_fn_obj hexconv(long n){
    retrun long_fn_obj( (ostream& (*)(ostream&, long )) hexconv, n); // 這里強制類型轉換,使用第一個版本的重載.
}

現在則可以使用hexconv了:

cout << hexconv(m) << " " << hexconv(n);

簡化

我們將上面實現的hexconv進行泛型化.

  1. 首先實現函數對象的模板類
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;
};
  1. 應用器模板:
template <class stype, class vtype>
stype& operator<<(stype& o, const func_obj<stype, vtype>& im) {
    return im(o);
}
  1. 重寫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ā)現他的實現跟我們的實現基本上是一致的.

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末靴拱,一起剝皮案震驚了整個濱河市蕾殴,隨后出現的幾起案子师崎,更是在濱河造成了極大的恐慌咐柜,老刑警劉巖师骗,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件黔龟,死亡現場離奇詭異妇智,居然都是意外死亡,警方通過查閱死者的電腦和手機氏身,發(fā)現死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門巍棱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蛋欣,你說我怎么就攤上這事航徙。” “怎么了陷虎?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵到踏,是天一觀的道長杠袱。 經常有香客問我,道長窝稿,這世上最難降的妖魔是什么楣富? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮伴榔,結果婚禮上纹蝴,老公的妹妹穿的比我還像新娘。我一直安慰自己踪少,他們只是感情好塘安,可當我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著援奢,像睡著了一般兼犯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上集漾,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天免都,我揣著相機與錄音,去河邊找鬼帆竹。 笑死绕娘,一個胖子當著我的面吹牛,可吹牛的內容都是我干的栽连。 我是一名探鬼主播险领,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼秒紧!你這毒婦竟也來了绢陌?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤熔恢,失蹤者是張志新(化名)和其女友劉穎脐湾,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體叙淌,經...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡秤掌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了鹰霍。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片闻鉴。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖茂洒,靈堂內的尸體忽然破棺而出孟岛,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布渠羞,位于F島的核電站斤贰,受9級特大地震影響,放射性物質發(fā)生泄漏次询。R本人自食惡果不足惜腋舌,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望渗蟹。 院中可真熱鬧块饺,春花似錦、人聲如沸雌芽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽世落。三九已至淮腾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間屉佳,已是汗流浹背谷朝。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留武花,地道東北人圆凰。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像体箕,于是被迫代替她去往敵國和親专钉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內容