[讀書筆記]Universal References in C++11 part 5: 使用標準庫中的Type Trait來檢查傳入型別錯誤

 

Universal Reference(UR)由於是用template撰寫而成的,因此他會在編譯期根據傳入的形態動態產生所需的程式碼。這可以說是一把雙面刃,因為就介面的宣告來看,它可以傳入任何的形態,若是使用者傳入不是我們預期中形態的話,雖然編譯器會報錯,但同時也會傳出一大串難解的錯誤訊息。若是系統複雜一點,一下子噴出上千行的錯誤訊息也不是什麼奇怪的事。雖然認真追還是能追到錯誤的源頭,但程序猿們平常的生活已經夠苦悶了,為什麼不讓生活簡單一點呢?這時候我們就必須要藉助Concept Check的概念,來幫助我們減少錯誤訊息。什麼是Concept Check?可以參考我以前寫過的文章:

 

首先,在導入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的幫助,我們輕鬆的點擊錯誤訊息,並立刻跳到錯誤的那一行:

tuto_4_type_error1

 

可是,在追蹤錯誤的過程,我們可以看到有二個錯誤根本是用來擾亂我們心神的存在:

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

dorgon

dorgon

職業:LV3遊戲軟體工程師 為了追尋小時候玩遊戲的感動,而一頭栽入遊戲業界。 本來以撰寫遊戲劇本為主要志向,但回過神來才發現已經踏入程序猿的不歸路。 專長為client端跨平台遊戲開發架構與自動建置流程,主要使用引擎為cocos2d-x與UnrealEngine4。

More Posts - Website

Follow Me:
FacebookLinkedIn

有什麼想法嗎?請發表你的看法