自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

直擊VS2010中的std::tr1 bind庫BUG

開發(fā) 后端
今天我們將看到的是Visual Studio 2010中一個BUG被捉住的過程。也就是std::tr1 bind庫BUG。

前兩天發(fā)現(xiàn)了VC2010 tr1庫中bind實現(xiàn)的一個bug,當時只是作了記錄,沒有詳細分析.但作為一個QA,不找出問題所在實在不算稱職,于是就有了這篇捉蟲記.

閑言少敘,書歸正傳,tr1庫就不多作介紹了,有興趣的同學(xué)可以去 wikipedia上看.bind,顧名思義,就是把參數(shù)與函數(shù)綁定,以利于我們進行函數(shù)式編程,是從boost的bind庫引入的,對bind不是很了解的可以看陳碩同學(xué)的這篇

以boost::function和boost:bind取代虛函數(shù)

假定筆者是 VC2010的QA,在做bind的功能測試,開始用gtest寫test case, (筆者一般用gtest作為C++測試框架,就寫到文章中了,但ms肯定不會用啦.筆者不在ms,請勿對號入座)

先寫幾個簡單的函數(shù)供測試之用. 

  1. int Add(int left, int right)  
  2. {  
  3. return left + right;  
  4. }  
  5. int Sub(int left, int right)  
  6. {  
  7. return left - right;  
  8. }  
  9. int Mul(int left, int right)  
  10. {  
  11. return left * right;  

再寫測試用例.先是幾個簡單的

1 把值綁定到函數(shù)指針

  1. TEST(Bind, FundPtr_Values)   
  2. {   
  3. auto f0 = std::tr1::bind(Sub, 5, 3);   
  4. ASSERT_EQ(2, f0());   

2 把值綁定到仿函數(shù)

  1. TEST(Bind, Functor_Values)   
  2. {   
  3. auto f0 = std::tr1::bind(std::minus<int>(), 6, 4);   
  4. ASSERT_EQ(2, f0());   

3 把占位符綁定到函數(shù)指針

  1. TEST(Bind, FuncPtr_Placeholder)  
  2. {  
  3. auto f0 = std::tr1::bind(Sub, std::tr1::placeholders::_1, 8);  
  4. ASSERT_EQ(12, f0(20));  

4 將占位符綁定到仿函數(shù)

  1. TEST(Bind, Functor_Placeholder)  
  2. {  
  3. auto f0 = std::tr1::bind(std::minus<int>(), std::tr1::placeholders::_2, std::placeholders::_1);  
  4. ASSERT_EQ(12, f0(8, 20));  

運行測試

  1. testing::InitGoogleTest(&argc, argv);  
  2. int ret = RUN_ALL_TESTS(); 

一切OK.

來個復(fù)雜的,將bind返回結(jié)果再bind到函數(shù),用于進行高階函數(shù)演算.

組合函數(shù)指針

  1. TEST(Bind, SquareDiff_FuncPtr)  
  2. {  
  3. auto f0 = std::tr1::bind(Mul,   
  4. std::tr1::bind(Add, std::tr1::placeholders::_1, std::tr1::placeholders::_2),  
  5. std::tr1::bind(Sub, std::tr1::placeholders::_1, std::tr1::placeholders::_2));  
  6. ASSERT_EQ(16, f0(5, 3));  

運行,一切OK,

再來一個仿函數(shù)版本

組合仿函數(shù)

  1. TEST(Bind, Squarediff_Functorptr)  
  2. {  
  3. auto f0 = std::tr1::bind(std::multiplies<int>(),  
  4. std::tr1::bind(std::plus<int>(), std::tr1::placeholders::_1, std::tr1::placeholders::_2),  
  5. std::tr1::bind(std::minus<int>(), std::tr1::placeholders::_1, std::tr1::placeholders::_2));  
  6. ASSERT_EQ(16, f0(5, 3));  

編譯 , wow,這是啥啊

Error    1    error C2664: 'int std::multiplies<_Ty>::operator ()(const _Ty &,const _Ty &) const' : cannot convert parameter 1 from 'std::tr1::_Bind_fty<_Fty,_Ret,_BindN>' to 'const int &'    c:\program files\microsoft visual studio 10.0\vc\include\xxcallobj    13    1    boostDemo 

發(fā)現(xiàn)問題了, 偶兩眼開始放光.

現(xiàn)在就開bug么,不,太早了,總得指明問題在哪兒吧.是編譯器還是tr1庫實現(xiàn)的問題?怎么辦?拿另一個庫boost試試,正常

通過就是tr1的問題了

  1. TEST(Bind, SquareDiff_BoostFunctor)  
  2. {  
  3. auto f0 = boost::bind(std::multiplies<int>(),  
  4. boost::bind(std::plus<int>(), _1, _2),  
  5. boost::bind(std::minus<int>(),_1, _2));  
  6. ASSERT_EQ(16, f0(5, 3));  

編譯運行,一切正常.

現(xiàn)在可以肯定是tr1庫的問題么?還不能.也許對仿函數(shù)綁定是boost的擴展而tr1準標準并不支持呢?

打開標準,看bind參數(shù)的說明 template<class F, class T1, class T2, ...., class TN>

unspecified bind(F f, T1 t1, T2 t2, ..., TN tN);

1 Requires: F and Ti shall be CopyConstructible. INVOKE (f, w1, w2, ..., wN) ([3.3]) shall be a valid expression

for some values w1, w2, ..., wN.

2 Returns: A forwarding call wrapper g with a weak result type ([3.3]). The effect of g(u1, u2, ..., uM) shall

be INVOKE (f, v1, v2, ..., vN, result_of<F cv (V1, V2, ..., VN)>::type), where cv represents

the cv-qualifiers of g and the values and types of the bound arguments v1, v2, ..., vN are determined as

specified below.

template<class R, class F, class T1, class T2, ...., class TN>

unspecified bind(F f, T1 t1, T2 t2, ..., TN tN);

3 Requires: F and Ti shall be CopyConstructible. INVOKE (f, w1, w2, ..., wN) shall be a valid expression for

some values w1, w2, ..., wN.

4 Returns: A forwarding call wrapper g with a nested type result_type defined as a synonym for R. The effect of

g(u1, u2, ..., uM) shall be INVOKE (f, v1, v2, ..., vN, R), where the values and types of the bound

arguments v1, v2, ..., vN are determined as specified below.

5 The values of the bound arguments v1, v2, ..., vN and their corresponding types V1, V2, ..., VN depend on

the type of the corresponding argument ti of type Ti in the call to bind and the cv-qualifiers cv of the call wrapper g as

follows:

— if ti is of type reference_wrapper<T> the argument is ti.get() and its type Vi is T&;

— if the value of std::tr1::is_bind_expression<Ti>::value is true the argument is ti(u1, u2, ...,

uM) and its type Vi is result_of<Ti cv (U1&, U2&, ..., UM&)>::type;

— if the value j of std::tr1::is_placeholder<Ti>::value is not zero the argument is uj and its type Vi is

Uj&;

— otherwise the value is ti and its type Vi is Ti cv &. 

這里參數(shù)t,v,W來回出現(xiàn),挺繞的,書讀百遍,其義自現(xiàn) f為要綁定的函數(shù),其參數(shù)為 v1,v2,…,vN,類型為V1,V2,…,VN

bind 函數(shù)參數(shù)為t1, t2, …,tN,類型為T1,T2,…,TN

g 為bind的返回值,參數(shù)為參數(shù)為u1, u2,…uM,類型為U1, U2,…,UM.

g(u1, u2,…uM)會被轉(zhuǎn)發(fā)給f,轉(zhuǎn)發(fā)效果相當于f(v1, v2, …,vN)

vi 如下確定

如果對應(yīng)bind函數(shù)參數(shù)ti類型為reference_wrapper<T>, 則vi為ti.get(),類型為T&
如果std::tr1::is_bind_expression<Ti>::value為true,即ti是bind的返回值,則vi為ti(u1,u2,…uM),類型為result_of<Ti cv(U1&,U2&,…,UM)>::type
如果std::tr1::is_placeholder<Ti>::value非0,則參數(shù)vi為uj,類型為 Uj&
否則vi值為ti,類型為Ti cv& 

現(xiàn)在就很明了了,一個bind的返回值r1可以作為另一個bind的參數(shù)a1,在轉(zhuǎn)發(fā)時轉(zhuǎn)發(fā)的是r1(u1, u2,…,uM),即測試的代碼是符合標準的.那為什么通不過呢?再加上兩個測試,看看bind的返回值類型究竟能否讓 is_bind_expression<T>::value為true

is_bind_expression測試

  1. template<class T>  
  2. bool IsBindResult(const T& v)  
  3. {  
  4. return std::tr1::is_bind_expression<T>::value;  
  5. }  
  6. TEST(IsBindResult, BindFuncPtr)  
  7. {  
  8. ASSERT_TRUE(IsBindResult(std::tr1::bind(Add, std::tr1::placeholders::_1, 2)));  
  9. }  
  10. TEST(IsBindResult, BindFunctor)  
  11. {  
  12. ASSERT_TRUE(IsBindResult(std::tr1::bind(std::plus<int>(), std::tr1::placeholders::_1, 2)));  

編譯,運行

clip_image001

結(jié)果再次讓偶大躍眼鏡,居然bind的返回值沒能通過is_bind_expression的測試,怎么回事,打開is_bind_expression 的源碼來看

is_bind_expression定義

  1. template<class _Tx>  
  2. struct is_bind_expression  
  3. {   
  4. static const bool value = false;  
  5. };  
  6. template<class _Result_type,  
  7. class _Ret,  
  8. class _BindN>  
  9. struct is_bind_expression<  
  10. _Bind<_Result_type, _Ret, _BindN> >  
  11. {   
  12. static const bool value = true;  
  13. }; 

這里對模板作了一個特化,如果bind的模板參數(shù)是 _Bind<_Result_type, _Ret, _BindN>類型,則value為true,否則false.

看上去很美,沒問題,那為什么測試會失敗?難道bind(Add,…)返回的是_Bind類型而 bind(plus<int>(),…)返回的不是?接著看bind實現(xiàn):

函數(shù)指針版

bind到函數(shù)指針的返回類型

  1. template<class _Rx  
  2. _C_CLASS_FARG0  
  3. _C_CLASS_ARG0> inline  
  4. _Bind<_Rx,  
  5. _Rx,  
  6. _BINDN<_Callable_fun<_Rx(__cdecl * const)(_FARG0_FARG1)> _C_ARG0_ARG1> >  
  7. bind(_Rx(__cdecl * const _Val)(_FARG0_FARG1) _C_ARG0_A0)  
  8. // bind to pointer to function  
  9. typedef _Callable_fun<_Rx(__cdecl * const)(_FARG0_FARG1)> _Callable;  
  10. typedef _BINDN<_Callable _C_ARG0_ARG1> _MyBind;  
  11. return (_Bind<_Rx, _Rx, _MyBind>(_MyBind(_Val _C_A0_A1)));  

返回的的確是_Bind類型,

仿函數(shù)版

bind到仿函數(shù)的返回類型

  1. template<class _Fty  
  2. _C_CLASS_ARG0> inline  
  3. _Bind_fty<_Fty,  
  4. _Notforced,  
  5. _BINDN<_Callable_obj<_Fty> _C_ARG0_ARG1> >  
  6. bind(_Fty _Val _C_ARG0_A0)  
  7. // bind to UDT  
  8. typedef _BINDN<_Callable_obj<_Fty> _C_ARG0_ARG1> _MyBind;  
  9. return (_Bind_fty<_Fty, _Notforced, _MyBind>(_MyBind(_Val _C_A0_A1)));  

返回的卻是_Bind_fty類型, 難道_Bind_fty不是_Bind類型導(dǎo)致is_bind_expression返回false?接著看定義

_BindN和_Bind_fty的定義

  1. template<class _Ret,  
  2. class _BindN>  
  3. class _Bind<_Notforced, _Ret, _BindN>  
  4. public _Bind_base<_Ret, _BindN>  
  5. {   
  6. public:  
  7. _Bind(_BindN _B0)  
  8. : _Bind_base<_Ret, _BindN>(_B0)  
  9. {   
  10. }  
  11. };  
  12. template<class _Fty,  
  13. class _Ret,  
  14. class _BindN>  
  15. class _Bind_fty  
  16. public _Wrap_result_type<(sizeof (::std:: tr1::_Has_result_type((_Fty *)0)) == sizeof (::std:: tr1::_Yes)), _Fty>,  
  17. public _Bind_base<_Ret, _BindN>  
  18. {   
  19. public:  
  20. _Bind_fty(_BindN _B0)  
  21. : _Bind_base<_Ret, _BindN>(_B0)  
  22. {   
  23. }  
  24. }; 

果然如此,真相大白.is_bind_expression不認為 bind(functor…)是bind表達式,故會直接將其作為類型為_Bind_fty的參數(shù)轉(zhuǎn)發(fā)為 logical_and,導(dǎo)致編譯出錯.

找到錯誤原因,可以開bug了 Title: std::tr1::bind can't bind a functor as an argument to another functor

How found: manual test

Build version:VC 1010 01019-532-2002102-70993

OS: Windows XP

Repro steps:

Run following code:

出錯代碼

  1. TEST(Bind, Squarediff_Functorptr)  
  2. {  
  3. auto f0 = std::tr1::bind(std::multiplies<int>(),  
  4. std::tr1::bind(std::plus<int>(), std::tr1::placeholders::_1, std::tr1::placeholders::_2),  
  5. std::tr1::bind(std::minus<int>(), std::tr1::placeholders::_1, std::tr1::placeholders::_2));  
  6. ASSERT_EQ(16, f0(5, 3));  
  7. }  
  8. Expected:  
  9. Code got compiled and test passed. See n1836  
  10. Result:  
  11. Build break 
  12.  
  13. Note:  
  14.  
  15. The result of std::tr1::bind(std::minus<int>(), std::tr1::placeholders::_1, std::tr1::placeholders::_2) 
  16. is of type _Bind_fty, which will make is_bind_expression<_Bind_fty>::value = false. Suggestion: modify the 
  17. return type of bind or change is_bind_expression to make is_bind_expression<_Bind_fty>::value to be true.  

提交bug, Ok.

結(jié)束了么?還沒有,需要編寫一個測試來驗證這個問題并加到測試列表里去,可是直接寫會導(dǎo)致編譯出錯,怎么辦呢?

有辦法,

將如下代碼保存到一個文件去

測試代碼

  1. #include <iostream>  
  2. #include <conio.h>   
  3. #include <tchar.h>  
  4. int main()   
  5. {   
  6. auto f0 = std::tr1::bind(std::multiplies&lt;int&gt;(),   
  7. std::tr1::bind(std::plus&lt;int&gt;(), std::tr1::placeholders::_1, std::tr1::placeholders::_2),   
  8. std::tr1::bind(std::minus&lt;int&gt;(), std::tr1::placeholders::_1, std::tr1::placeholders::_2));   
  9. return f0(5, 3);   

調(diào)用cl編譯這個文件,如果能得到一個可執(zhí)行程序并且執(zhí)行后返回16則測試通過,否則測試失敗.

代碼就不在這里列舉了.

寫完測試,添加到daily build的test list中去,什么時候問題解決了從測試結(jié)果上就能看出來了.

1 文中寫的找bug的過程是直線式的邏輯,思路很清晰,實際我在跟代碼的時候就像在一個迷宮里打轉(zhuǎn),只看見出了錯誤,不知道什么地方錯了,只好用各種手段, 想先找出bind在綁定函數(shù)指針時究竟干了什么,再來推理為什么綁定仿函數(shù)會出錯,根本不是象文中寫的那 樣直接推斷到is_bind_expression的問題.

VC2010里tr1庫的實現(xiàn)實在不好閱讀,滿篇全是宏,連函數(shù)調(diào)用傳了幾個參數(shù)都搞不清楚,我只好先用/E選項把預(yù)編譯的文件輸出出來,在跟蹤代碼時一邊看預(yù)處理后的代碼一邊看匯編,無論哪個都比tr1自己的源代碼好讀.

不過這一番跟蹤下來收獲倒也頗豐,

1) 基本清楚了bind的實現(xiàn),可以另外寫篇博客來談

2) 明白了RVO(返回值優(yōu)化)的原理.在跟蹤bind匯編代碼時時發(fā)現(xiàn)只要三個參數(shù),卻push了四個,調(diào)用完成后也是ret 10返回,跟了下去才發(fā)現(xiàn)是做的RVO

2 文中用到了ASSERT宏進行測試,也算是對前些天對我這篇金山衛(wèi)士代碼批評評論里大家對ASSERT質(zhì)疑的一個回應(yīng)吧.c++庫里的assert的作用是及時發(fā)現(xiàn)錯誤反饋給程序員, 會打斷程序執(zhí)行,而測試框架里的ASSERT則是斷言測試是否符合預(yù)期,把結(jié)果傳遞給測試框架,再由測試框架記錄后反饋給程序員.我原來以為在那個上下文都是測試情況下assert的語義應(yīng)當不言自明的是指后者,沒想到還是有很多讀者誤以為是c++庫里的assert.這是我沒有把話說清楚,假定自己知道的受眾也知道,忽略了背景的區(qū)別帶來的對同一個名詞不同的理解,是我經(jīng)常犯的一個錯誤,要努力改正.

3 QA除了寫測試用例,用工具和腳本進行測試外還可以在項目過程中參與更多.一個好的QA應(yīng)當有不弱于Dev的編碼能力, 有能力復(fù)查Dev設(shè)計和編碼,直接從中發(fā)現(xiàn)問題,以及在測試中發(fā)現(xiàn)問題時有能力定位bug源頭并給出參考解決方案.QA還要有縝密的思維和想象能力,對邊界條件、各種邏輯組合和極端情況能去構(gòu)造和評估其對功能的影響,因為Dev一般習(xí)慣于正常情況下的邏輯,邊界情況雖然也會考慮,但還是不會有QA想的全面.遺憾的是目前國內(nèi)開發(fā)團隊中這樣的QA還是比較少見.

原文鏈接:http://www.cnblogs.com/MichaelPeng/archive/2010/12/27/ABugReportOnVC2010_std_tr1_bind.html

【編輯推薦】 

  1. Visual Studio自定義調(diào)整窗體的兩個小技巧
  2. Visual Studio 2010中關(guān)于C#的幾點改進
  3. Visual Studio 2010及.Net 4新功能一覽
  4. 提高效率 用好Visual Studio 2010自定義代碼段
     

 

責(zé)任編輯:彭凡 來源: 博客園
相關(guān)推薦

2011-08-16 14:50:05

CMFCToolBarVS2010

2011-06-23 10:16:55

VS2010 QT 4.7.2 QT

2009-12-02 14:19:09

VS 2010產(chǎn)品

2009-11-11 11:29:37

VS2010 Auto

2009-07-28 10:00:47

VS2010 beta

2009-12-11 15:13:15

VS 2010驅(qū)動

2010-01-14 10:56:43

Visual C++

2009-12-15 17:55:54

VS2010 Ulti

2009-12-11 14:16:11

VS2010 Ulti

2009-12-15 17:42:29

2011-01-18 08:55:20

IntelliTracVS2010

2010-04-18 19:51:03

VS2010

2009-12-02 14:05:17

VS2010程序

2009-12-18 10:24:28

VS 2010代碼

2009-06-10 22:41:47

Visual StudVS2010ASP.NET MVC

2009-05-26 10:01:51

Visual StudVisual C++C++0x

2010-05-06 17:46:47

2011-06-23 09:54:00

VS2010 Qt 4.7.2 Qt

2009-05-21 14:42:09

.NET 4.0VS2010Visual Stud

2009-12-11 14:21:55

VS2010程序
點贊
收藏

51CTO技術(shù)棧公眾號