Universal Reference(UR)由於是用template撰寫而成的,因此他會在編譯期根據傳入的形態動態產生所需的程式碼。這可以說是一把雙面刃,因為就介面的宣告來看,它可以傳入任何的形態,若是使用者傳入不是我們預期中形態的話,雖然編譯器會報錯,但同時也會傳出一大串難解的錯誤訊息。若是系統複雜一點,一下子噴出上千行的錯誤訊息也不是什麼奇怪的事。雖然認真追還是能追到錯誤的源頭,但程序猿們平常的生活已經夠苦悶了,為什麼不讓生活簡單一點呢?這時候我們就必須要藉助Concept Check的概念,來幫助我們減少錯誤訊息。什麼是Concept Check?可以參考我以前寫過的文章:
[insert page=’233′ display=’link’]
首先,在導入Concept Check前的程式碼如下:
class URWidget{ public: URWidget(){}; template<typename T> void setResource(T&& newResoure) // universal reference { m_resourcePtr = std::forward<T>(newResoure); } template<typename T> void setName(T&& newName) // universal reference { m_name = std::forward<T>(newName); } static URWidget makeWidget() // Moving version of makeWidget { URWidget w; //do somthing return std::move(w); // move w into return value } // (don't do this!) private: std::string m_name; std::shared_ptr<Resource> m_resourcePtr; }; int main(){ URWidget widget; auto newRes = std::make_shared<Resource>("test"); widget.setResource(newRes); //ok widget.setResource("test"); //oops! should compile error return 0; }
我們可以看到,如果使用者丟入下面這一行的話:
widget.setResource(“test”); //oops! should compile error
編譯器馬上噴出一堆意義不明的錯誤:
>c:\users\dorgonman\documents\workspace\universalreference\tuto4_ur_and_type_trait\source.cpp(70): error C2679: binary '=' : no operator found which takes a right-hand operand of type 'const char [5]' (or there is no acceptable conversion) 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\memory(613): could be 'std::shared_ptr<Resource> &std::shared_ptr<Resource>::operator =(const std::shared_ptr<Resource> &) throw()' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\memory(595): or 'std::shared_ptr<Resource> &std::shared_ptr<Resource>::operator =(std::shared_ptr<Resource> &&) throw()' 1> while trying to match the argument list '(std::shared_ptr<Resource>, const char [5])' 1> c:\users\dorgonman\documents\workspace\universalreference\tuto4_ur_and_type_trait\source.cpp(104) : see reference to function template instantiation 'void URWidget::setResource<const char(&)[5]>(T)' being compiled 1> with 1> [ 1> T=const char (&)[5] 1> ] ========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
這時候苦命的程序猿就要想辦法去找出到底哪一行出了錯,藉由現代強大IDE的幫助,我們輕鬆的點擊錯誤訊息,並立刻跳到錯誤的那一行:
可是,在追蹤錯誤的過程,我們可以看到有二個錯誤根本是用來擾亂我們心神的存在:
c:\program files (x86)\microsoft visual studio 12.0\vc\include\memory(613): could be 'std::shared_ptr<Resource> &std::shared_ptr<Resource>::operator =(const std::shared_ptr<Resource> &) throw()' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\memory(595): or 'std::shared_ptr<Resource> &std::shared_ptr<Resource>::operator =(std::shared_ptr<Resource> &&) throw()' 1> while trying to match the argument list '(std::shared_ptr<Resource>, const char [5])'
第一次看到的時候,相信沒有人可以馬上反應過來:這memory(613)行到底是錯啥。我們必須一步一步的追到錯誤的最底層才會發現,原來這個function不能丟string進去。這裡的例子還算是簡單,就讓我們試試稍微增加一點系統複雜度的話,看看會怎麼樣:
class URWidget{ public: URWidget(){}; template<typename T> void setResource(T&& newResoure) // universal reference { setResource2(std::forward<T>(newResoure)); // m_resourcePtr = std::forward<T>(newResoure); } template<typename T> void setName(T&& newName) // universal reference { m_name = std::forward<T>(newName); } static URWidget makeWidget() // Moving version of makeWidget { URWidget w; //do somthing return std::move(w); // move w into return value } // (don't do this!) private: template<typename T> void setResource2(T&& newResoure){ // universal reference setResource3(std::forward<T>(newResoure)); } template<typename T> void setResource3(T&& newResoure){ // universal reference setResource4(std::forward<T>(newResoure)); } template<typename T> void setResource4(T&& newResoure){ // universal reference m_resourcePtr = std::forward<T>(newResoure); } private: std::string m_name; std::shared_ptr<Resource> m_resourcePtr; };
傳遞參數的路徑如下:setResource=>setResource2=>setResource3=>setResource4。編譯後錯誤訊息馬上增加了:
1>------ Build started: Project: Tuto4_UR_and_Type_Trait, Configuration: Debug Win32 ------ 1> Source.cpp 1>c:\users\dorgonman\documents\workspace\universalreference\tuto4_ur_and_type_trait\source.cpp(105): error C2679: binary '=' : no operator found which takes a right-hand operand of type 'const char [5]' (or there is no acceptable conversion) 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\memory(613): could be 'std::shared_ptr<Resource> &std::shared_ptr<Resource>::operator =(const std::shared_ptr<Resource> &) throw()' 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\memory(595): or 'std::shared_ptr<Resource> &std::shared_ptr<Resource>::operator =(std::shared_ptr<Resource> &&) throw()' 1> while trying to match the argument list '(std::shared_ptr<Resource>, const char [5])' 1> c:\users\dorgonman\documents\workspace\universalreference\tuto4_ur_and_type_trait\source.cpp(101) : see reference to function template instantiation 'void URWidget::setResource4<const char(&)[5]>(T)' being compiled 1> with 1> [ 1> T=const char (&)[5] 1> ] 1> c:\users\dorgonman\documents\workspace\universalreference\tuto4_ur_and_type_trait\source.cpp(94) : see reference to function template instantiation 'void URWidget::setResource3<const char(&)[5]>(T)' being compiled 1> with 1> [ 1> T=const char (&)[5] 1> ] 1> c:\users\dorgonman\documents\workspace\universalreference\tuto4_ur_and_type_trait\source.cpp(72) : see reference to function template instantiation 'void URWidget::setResource2<const char(&)[5]>(T)' being compiled 1> with 1> [ 1> T=const char (&)[5] 1> ] 1> c:\users\dorgonman\documents\workspace\universalreference\tuto4_ur_and_type_trait\source.cpp(121) : see reference to function template instantiation 'void URWidget::setResource<const char(&)[5]>(T)' being compiled 1> with 1> [ 1> T=const char (&)[5] 1> ] ========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
嗯…頭越來越痛了。當系統複雜度增加的時候,可以預見錯誤訊息的行數將會以指數複雜度噴出來。可不可以只顯示我到底錯在哪裡就行了?第一個想法,可能會想寫一個concept check的function來幫我們達成這件事。其實大可放心,因為標準庫中的Type Trait已經實作了幾乎所有我們需要的功能。在這裡的例子,我們只需要檢查的是傳進來的參數是不能夠assign給m_resourcePtr,因此我們只要增加一行實作:
template<typename T> void setResource(T&& newResoure) // universal reference { static_assert(std::is_convertible<T, decltype(m_resourcePtr) >::value, "T can't assign to m_resourcePtr"); setResource2(std::forward<T>(newResoure)); }
錯誤訊息馬上只剩下二行:
1>------ Build started: Project: Tuto4_UR_and_Type_Trait, Configuration: Debug Win32 ------ 1> Source.cpp 1>c:\users\dorgonman\documents\workspace\universalreference\tuto4_ur_and_type_trait\source.cpp(69): error C2338: T can't assign to m_resourcePtr 1> c:\users\dorgonman\documents\workspace\universalreference\tuto4_ur_and_type_trait\source.cpp(116) : see reference to function template instantiation 'void URWidget::setResource<const char(&)[5]>(T)' being compiled 1> with 1> [ 1> T=const char (&)[5] 1> ] ========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
錯誤發生在source.cpp(116),原因是T can’t assign to m_resourcePtr。
太棒了!看來頭痛藥可以少吃一點了!
本篇程式碼可以在github下載: https://github.com/dorgonman/UniversalReference VS專案為Tuto4_UR_and_Type_Trait
Leave a Reply