1. 程式人生 > >C++ 實現 根據字串 呼叫同名函式

C++ 實現 根據字串 呼叫同名函式

需求: 希望根據使用者的輸入呼叫同名的函式。

因為不想寫各種 if else,所以就建立一個key為string,value為函式指標的map,根據string的值呼叫相應的函式。

以下程式碼在gcc 3.4.6下測試通過。

下面是程式碼的第一次實現:

<code>

#include<iostream>

#include<map>
#include<string>


void buildMap();
class Image{
public:
    Image() {}

    void func1() { std::cout << "func1 called.\n"; }
    void func2() { std::cout << "func2 called.\n"; }
};


typedef void (Image::*pfunc)();                                // define function pointer type
std::map<std::string, pfunc> strFuncMap;
void buildMap() { strFuncMap["func1"] = &Image::func1; strFuncMap["func2"] = &Image::func2; }
void callFunc(Image& img, const std::string& str) { if(strFuncMap.count(str)) (img.*strFuncMap[str])(); else std::cout << "unsupported function str.\n";}
int main() {

    Image img;

    buildMap(); 

    callFunc(img, "func2");
    callFunc(img, "func1");
    callFunc(img, "func3");
    return 0;

}

</code>

但是這樣用的是一些自由函式和變數,我想把它們放到類裡面去。因為我覺得不需要每一個物件都建立一個map,所以把該map設定成了static型別。

注意加紅的部分  (this->*(strFuncMap[str]))() ,是利用該map進行函式呼叫的部分,試了好多種方式,只有這樣才沒有報編譯錯誤。

另外,還有std::map<std::string, Image::pfunc> Image::strFuncMap;  

即使不初始化也不能刪除,否則會有連線錯誤。這表示對strFuncMap的定義,在類中是對它的宣告。

<code>

class Image{
public:
    typedef void (Image::*pfunc)();
    static void buildMap();


    Image() {}
    
    void callFunc(const std::string& str) { if(strFuncMap.count(str)) (this->*(strFuncMap[str]))(); else std::cout << "unsupported function " << str << std::endl;}


    void func1() { std::cout << "func1 called.\n"; }
    void func2() { std::cout << "func2 called.\n"; }


private:
    static std::map<std::string, pfunc> strFuncMap;
};


std::map<std::string, Image::pfunc> Image::strFuncMap;


void Image::buildMap() { strFuncMap["func1"] = &Image::func1; strFuncMap["func2"] = &Image::func2; }


int main() {
    Image img;
    img.buildMap();
    img.callFunc("func2");
    img.callFunc("func1");
    img.callFunc("func3");
    return 0;
}

</code>

但是,又有新問題,我想加入繼承和多型的功能,讓Image作為基類使用,使用者可以使用原來藉口呼叫到合適的函式。

最開始的時候我是這樣想的,把想要變成virtual的成員函式變成virtual,然後把buildMap改為非static函式並放在基類和派生類的建構函式中,把buildMap函式的內容從

void Image::buildMap() { strFuncMap["func1"] = &Image::func1; strFuncMap["func2"] = &Image::func2; }

改成

void Image::buildMap() { strFuncMap["func1"] = &func1; strFuncMap["func2"] = &func2; }

因為在我的理解裡Image::func1就會確定是呼叫的基類的函式,然後我就幻想著去掉作用域的符合buildMap自己能夠儲存到正確版本的函式。結果g++不給面子,報錯了。

ISO C++ forbids taking the address of an unqualified or parenthesized non-static member function to form a pointer to member function.
也就是不允許這種寫法。
然後我想那就把buildMap也改成virtual型別,然後把對它的呼叫放在建構函式中。(雖然effective c++中建議不要在建構函式中呼叫virtual函式,但是即使基類的建構函式中呼叫的buildMap不會下降到派生類的buildMap,我會在派生類的建構函式中再呼叫派生類版本的buildMap,所以我猜應該可以的。)將它在派生類中的定義改成(假設AdvancedImage為派生類,且func1為virtual函式):
void Image::buildMap() { strFuncMap["func1"] = &AdvancedImage::func1; strFuncMap["func2"] = &Image::func2; }
事實證明這樣也是不行的,g++表示:
cannot convert `void (AdvancedImage::*)()' to `void (Image::*)()' in assignment
啊,那就是說基類和派生類的成員函式型別不同,不能賦值。
它表示成員函式的指標是很特殊的東西,支援多型。啊哈,也就是說,&Image::func1這個東西不是指代基類的fun1,而是自己可以匹配到合適的派生類函式?
所以,第三次程式碼如下:
<code>
class Image{
public:
    typedef void (Image::*pfunc)();
    void buildMap();


    Image() { buildMap(); }
    
    void callFunc(const std::string& str) { if(strFuncMap.count(str)) (this->*(strFuncMap[str]))(); else std::cout << "unsupported function " << str << std::endl;}


    virtual void func1() { std::cout << "base class func1 called.\n"; }
    void func2() { std::cout << "func2 called.\n"; }


protected:
    static std::map<std::string, pfunc> strFuncMap;
};


std::map<std::string, Image::pfunc> Image::strFuncMap;
void Image::buildMap() { strFuncMap["func1"] = &Image::func1; strFuncMap["func2"] = &Image::func2; }


class AdvancedImage : public Image{
public:
     virtual void func1() {std::cout << "drived class func1 called.\n"; }
};






int main() {
    Image img;
    img.callFunc("func2");
    img.callFunc("func1");
    img.callFunc("func3");
    AdvancedImage advImg;
    advImg.callFunc("func2");
    advImg.callFunc("func1");
    advImg.callFunc("func3");
    Image *pImg = &advImg;
    pImg->callFunc("func2");
    pImg->callFunc("func1");
    pImg->callFunc("func3");
    return 0;
}
</code>
結果果然可以輸出正確的結果了:
func2 called.
base class func1 called.
unsupported function func3
func2 called.
drived class func1 called.
unsupported function func3
func2 called.
drived class func1 called.
unsupported function func3

雖然實現了功能,但是感覺還是暈暈乎乎一無所知的感覺。可能是因為不理解面向物件技術實現原理吧。