《Effective Modern C++》翻译--条款4:了解怎样查看推导出的类型
條款4:了解怎樣查看推導出的類型
那些想要了解編譯器怎樣推導出的類型的人通常分為兩個陣營。
第一種陣營是實用主義者。他們的動力通常來自于編敲代碼過程中(比如他們還在調試解決中),他們利用編譯器進行尋找,并相信這個能幫他們找到問題的根源。另外一種是經驗主義者。他們正在探索條款1-3所描寫敘述的推導規則。
而且從大量的推導情景中確認他們預測的結果(“對于這段代碼,我覺得推導出的類型將會是…”),可是有時候。他們僅僅是想簡單的回答假設這樣,會怎么樣呢之類的問題?他們可能想知道假設我用一個universal reference(見條款26)替代一個左值的常量形參(比如在函數的參數列表中用T&&替代const T&)模板類型推導的結果會改變嗎?
無論你屬于哪一個陣營(二者都是合理的),你所要使用的工具取決于你想要在軟件開發的哪一個階段知道編譯器推導出的結果。我們會闡述3種可行的方法:在編輯代碼的時獲得推導的類型。在編譯時獲得推導的類型,在執行時獲得推導的類型。
IDE編輯器
當你在IDE中的編輯代碼時,在你將鼠標懸停在程序實體(比如變量,參數。函數等等)上的時候。編譯器顯示他們的類型。
比如,在以下的代碼中。
const int theAnswer = 42 ;auto x = theAnswer; auto y = &theAnswer;IDE編輯器非常可能會顯示出x的類型是int,y的類型是const int*。
對于這項工作。你的代碼不能過于復雜,由于是IDE內部的C++編譯器讓IDE提供了這一項信息。
假設編譯器不能充分理解并解析你的代碼,產生類型推導的結果,它就無法給你顯示類型推導的結果。
編譯器的診斷
一個有效的得知編譯器對某一類型推導出的結果方法是讓它產生一個編譯期的錯誤。由于錯誤的報告信息肯定會提到引起錯誤的類型。
假如,我們想要知道上一個代碼中的x和y被推導出的類型。我們首先聲明一個類模板,可是不定義它。代碼會像以下這樣:
template<typename T> //declaration only for TD class TD; //TD == "Type Displayer"試圖實例化這個模板會產生一個錯誤信息,由于沒有模板的定義和實例。為了要查看x和y的類型,僅僅須要用它們的類型實例化TD:
TD<decltype(x)> xType //elicit errors containing TD<decltype(y)> yType //x's and y's types;//see Item 3 for decltype info我使用這樣的形式的變量名:variableNameType。由于:它們趨向于產生足夠實用的錯誤信息。對于上面的代碼,當中一個編譯器的錯誤診斷信息例如以下所看到的(我高亮了我們想要的類型推導結果)
error: aggregate 'TD<int> xType' has incomplete type and cannot be defined error: aggregate 'TD<const int *>yType' has incomplete type and cannot be defined還有一個編譯器提供了一樣的信息,可是格式有所不同:
error: 'xType' uses undefined class 'TD<int>' error: 'yType' uses undefined class 'TD<const int *>'把格式上的不同放到一旁,我所測試的全部編譯器都提供了包含實用的類型錯誤診斷信息。
執行期間的輸出
利用printf方法(并非說我推薦你使用printf)顯示類型的信息不能在程序執行時期使用。可是它須要對輸出格式的全然控制。
難點是怎樣讓變量的類型能以文本的方式合理的表現出來。你可能會覺得“沒有問題”typeid和std::type_info::name會解決問題的。
你覺得我們能夠寫下以下的代碼來知道x和y 的類型:
std::cout << typeid(x).name() << '\n'; // display types for std::cout << typeid(y).name() << '\n'; // x and y這種方法依賴于typeid作用于一個對象上時,返回類型為std::type_info這一個事實,type_info有一個叫name的成員函數,提供了一個C風格的字符串(比如 const char*)來表示這個類型的名字。
調用std::type_info的name并不保證返回的東西一定是清楚明了的,可是會盡可能的提供幫助。
不同的編譯器提供的程度各有不同,比如:GNU和Clang編譯器將x的類型表示為”i”,將y的類型表示為”PKI”,一旦你了解i意味著int,pk意味著pointer to Konst const(兩個編譯器都提供一個C++ filt工具,來對這些重整后的名字進行解碼)。理解編譯器的輸出將變得easy起來,Microsoft的編譯器提供了更清楚的輸出,x的類型是int,y的類型是int const*.
由于對x和y顯示的結果是正確的,你可能會覺得問題已經攻克了。可是我們不能草率。想想以下這個更復雜的樣例:
template<typename T> // template function to void f(const T& param); // be called std::vector<Widget> createVec(); // factory function const auto vw = createVec(); // init vw w/factory return if (!vw.empty()) { f(&vw[0]); // call f }當你想知道編譯器推導出的類型是什么的時候。這段代碼更具有代表性,由于它牽涉到了一個用戶自己定義類型widget,一個std容器std::vector。一個auto變量,比如。你可能想知道模板參數T的類型。和函數參數f的類型。
使用typeid看起來是非常直接的方法。僅僅是在f中對你想知道的類型加上一些代碼:
template<typename T> void f(const T& param) { using std::cout; cout << "T = " << typeid(T).name() << '\n'; // show T cout << "param = " << typeid(param).name() << '\n'; // show param's type ... }GNU和Clang的執行結果是以下這樣:
T = PK6Widget param = PK6Widget我們已經知道PK意味著pointer to const,而6代表了類的名字中有多少個字母(Widget),所以這兩個編譯器告訴了我們T和param的類型都是const Widget*
微軟的編譯器提供了以下的結果
T = class Widget const * param = class Widget const *這三個編譯器都提供了一樣的信息。這也許暗示了結果應該是準確的。可是讓我們看的更仔細一點,在模板f中,param的類型被聲明為constT&,既然如此的話,param和T的類型一樣難道不讓人感到奇怪嗎,假設T的類型是int,param的類型應該是const int&,看,一點都不一樣。
令人悲哀的是std::type_info::name的結果并非可依賴的。在這個樣例中,三個編譯器對于param的結果都是不對的。此外。它們必須是錯誤的。由于標準(specification)規定被std::type_info::name處理的類型是被依照按值傳遞給模板對待的,像條款1解釋的那樣。這意味著假設類型本身是一個引用的話,引用部分是被忽略掉的,假設引用去掉之后還含有const,常量性也將被忽略掉,,這就是為什么const Widget* const &的類型被顯示為const Widget*,首先類型的引用部分被忽略了,接著結果的常量性也被忽略了。
相同令人傷心的是,IDE提供的類型信息相同也是不可靠的,或者說不是那么的實用,對于這個樣例,我所知道的編譯器將T的類型顯示為(這不是我編造出來的):
const std::_Simple_types<std::_Wrap_alloc<std::_Vec_base_types<Widget, std::allocator<Widget> >::_Alloc>::value_type>::value_type *將param的類型顯示為:
const std::_Simple_types<...>::value_type *const &這個顯示沒有T的那么嚇人了。中間的…僅僅是意味著IDE告訴你。我將T的類型顯示用…替代了。
我的理解是大多數顯示在這里的東西是由于typedef造成的,一旦你通過typedef來獲得潛在的類型信息,你會得到你所尋找的。但須要做一些工作來消除IDE最初顯示出的一些類型,幸運的話, 你的IDE編輯器會對這樣的代碼處理的更好。
在我的經驗中,使用編譯器的錯誤診斷信息來知道變量被推導出的類型是相對可靠的方法,利用修訂之后的函數模板f來實例化僅僅是聲明的模板TD。修訂之后的f看起來像以下這樣
template<typename T> void f(const T& param) { TD<T> TType; // elicit errors containing TD<decltype(param)> paramType; // T's and param's types … }GNU。Clang和Microsoft的編譯器都提供了帶有T和param正確類型的錯誤信息,當時顯示的格式各有不同,比如在GUN中(格式經過了一點輕微的改動)
error: 'TD<const Widget *> TType' has incomplete type error: 'TD<const Widget * const &> paramType' has incomplete type除了typeid
假設你想要在執行時獲得更正確的推導類型是什么,我們已經知道typeid并非一個可靠的方法,一個可行的方法是自己實現一套機制來完畢從一個類型到它的表示的映射,概念上這并不困難。你僅僅須要利用type trait和模板元編程的方法來將一個完整類型拆分開(使用std::is_const,std::is_ponter,std::is_lvalue_reference之類的type trait),你還須要自己完畢類型的每一部分的字符串表示(雖然你依然須要typeid和std::type_info::name來產生用戶自己定義格式的字符串表達)。
假設你常常須要使用這種方法,而且覺得花費在調試,文檔,維護上的努力是值得的。那么這是一個合理的方法(If you’d use such a facility often enough to justify the effort needed to write, debug,document, and maintain it, that’s a reasonable approach),可是假設你更喜歡那些移植性不是非常強的可是能輕易實現而且提供的結果比typeid更好的代碼的。 你須要注意到非常多編譯器都提供了語言的擴展來產生一個函數簽名的字符串表達,包含從模板中實例化的函數,模板和模板參數的類型。
比如。GNU和Clang都支持PRETTY_FUNCTION,Microsoft支持了FUNCSIG,他們代表了一個變量(在 GNU和Clang中)或是一個宏(在Microsoft中),假設我們將模板f這么實現的話
template<typename T> void f(const T& param) {#if defined(__GNUC__) //For GNU and std::cout << __PRETTY_FUNCTION__ << '\n'; // Clang #elif defined(_MSC_VER) std::cout << __FUNCSIG__ << '\n'; //For Microsoft #endif … }像之前那樣調用f,
std::vector<Widget> createVec(); // factory function const auto vw = createVec(); // init vw w/factory return if (!vw.empty()) { f(&vw[0]); //call f ... }在GNU中我們得到了以下的結果
void f(const T&) [with T = const Widget*]告訴我們T的類型被推導為const Widget*(和我們用typeid得到的結果一樣,可是前面沒有PK的編碼和類名前面的6),同一時候它也告訴我們f參數類型是const T&,假設我們依照這個格式擴展T,我們得到f的類型是const Widget * const&,和typeid的答案不同,可是和使用沒有定義的模板,產生的錯誤診斷信息中的類型信息一致。所以它是正確的。
Microsoft的 FUNCSIG提供了以下的輸出:
void __cdecl f<const classWidget*>(const class Widget *const &)尖括號中的類型是T被推導的類型,為const Widget*。
和我們用typeid得到的結果一樣。
括號內的類型是函數參數的類型。是const Widget* const&。和我們用typeid得到的結果不一樣。但相同和我們使用TD在編譯期得到的類型信息一致。
Clang的PRETTY_FUNCTION,雖然使用了和GNU一樣的名字,可是格式卻和GNU或是Microsoft的不一樣:
void f(const Widget *const &)它直接顯示出了參數的類型,可是須要我們自己去推導出T的類型被推導為了const Widget*(或者我們也能夠通過typeid來獲得T的類型)
IDE編輯器。編譯器的錯誤診斷信息,typeid和PRETTY_FUNCTION,FUNCSIG之類的語言擴展僅僅僅僅是幫助你弄明確編譯器推導出的結果是什么。可是最后,沒有什么能替代條款1-3中所描寫敘述的類型推導相關的推導規則。
請記住:
?能夠通過使用IDE編譯器、編譯錯誤信息、typeid、PRETTY_FUNCTION和FUNCSIG這樣的語言擴展等。查看類型推導。
?一些工具提供的類型推導結果可能既沒實用也不準確,所以理解C++類型推導的原則十分必要。
==============================================================
譯者凝視:
IDE 即Integrated Development Environment。是“集成開發環境”的英文縮寫,能夠輔助開發程序的應用軟件。
轉載于:https://www.cnblogs.com/gavanwanggw/p/7054562.html
總結
以上是生活随笔為你收集整理的《Effective Modern C++》翻译--条款4:了解怎样查看推导出的类型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何安装Genymotion虚拟机以及G
- 下一篇: 浏览器端已支持 ES6 规范(包括 ex