2013-12-10 28 views
3

所以我有一個變體類,我最近添加了存儲指向成員函數數據的功能。它使用下面的代碼來實現它。成員函數指針的解決方法是一個壞的破解?

class Variant 
{ 
    void* _value; 

    template <typename T1> 
    void Variant::ThisCall(T1* pThis) 
    { 
     typedef void(T1::* fptr)(); 
     fptr a; 
     int* iiVal = new (&a) int; 
     *iiVal = *((int*)_value); 
     (pThis->*a)(); 
    } 
}; 

// usage 
Variant myfunc = &SomeClass::SomeMethod; 
SomeClass* s = new SomeClass(); 
myfunc.ThisCall(s); 

我爲這個解決方案工作的最大的事情是指針到成員函數不能轉換爲void *。所以賦值運算符的種類基本上完成了這個操作的逆過程。它獲取給定的數據,將其作爲一個int指針(如果它是一個指針本身)並將int指針賦給void *,這是完全合法的。

所以我的問題是這樣的:爲什麼我覺得這是一個可怕的解決方案?我覺得這是一個很大的破解,它必須有一些嚴重的問題,但是我已經在這個問題上如此深刻了幾天,現在我看不到它過去了。謝謝!

[EDIT#1]

一位評論者指出,這可能不與虛擬方法的工作。我已經使用下面的代碼進行了測試,它似乎檢查出來。

class ImplA : public Base 
{ 
public: 
    virtual void Print() 
    { 
     cout << "ImplA print\n"; 
    } 
}; 

class ImplB : public Base 
{ 
public: 
    virtual void Print() 
    { 
     cout << "ImplB print\n"; 
    } 
}; 

class ImplC : public ImplA 
{ 
public: 
    virtual void Print() 
    { 
     cout << "ImplC print\n"; 
    } 
}; 

// usage 
Variant x = &Base::Print; 
auto b = new ImplA; // replace ImplA with ImplB or ImplC and it works as expected 
x.ThisCall(b); 

對於一些額外的信息,我使用VS2010作爲我的編譯器。謝謝!

[編輯#2]

來提供內容,我一直在這個變體類很短的一段時間,現在,我試圖使它支持任何你能在它扔。在這樣做的時候,我想到了函數指針和成員函數指針。然後我想出了這個問題,並想知道這個解決方案實際上有多堅實。鑄造和語法是我的第一個紅旗,但是我認爲,由於它所擁有的數據的差異,這只是與領土一起出現。但我仍然不相信這是如何工作的。

+1

*一個問題:*可能不適用於[指向虛擬成員函數的指針](http://coliru.stacked-crooked.com/a/4691b73521b8145e)。 'void *'不需要與任何*函數指針IIRC具有相同的大小。 – dyp

+1

IIRC強制轉換函數指針無效,或者導致UB。 – RedX

+0

@DyP:我剛剛檢查了虛擬成員函數,它確實有效。我會發布一些我以前用來證明的代碼。不過謝謝你! –

回答

1

忽略走樣違規行爲,這本身就使你的代碼是非法的,你在做什麼是相同的:

typedef void(T1::* fptr)(); 
    fptr a; 
    memcpy(&a, _value, sizeof(int)); 
    (pThis->*a)(); 

這應該是顯而易見的,爲什麼這是不可移植的;不能保證fptrint的大小相同,因此您可能會部分初始化其存儲或將其溢出。

如果用sizeof(fptr)替換sizeof(int),並確保存儲是_value點是大到足以容納一個fptr這將是合法的。但是,您仍然應該使用memcpy而不是別名; memcpy保證工作(3.9p2),而混疊會導致難以檢測到的錯誤,這些錯誤通常會在優化時出現或改變行爲。

+0

我喜歡你的答案。唯一真正關注這個問題的人。謝謝。 –

0

就像評論中提到的,這段代碼不是非常便攜和安全的。如果你只是存儲指向功能,我建議使用std ::函數或升壓::功能包裝:

template <typename T> 
class Variant { 
    std::function<void(T*)> fun; 
public: 
    Variant(void (T:: *ptr)()) : fun(ptr) { 
    } 

    void ThisCall(T* pThis) { 
     fun(pThis); 
    } 
}; 

Variant<SomeClass> myfunc = &SomeClass::SomeMethod; 
SomeClass* s = new SomeClass(); 
myfunc.ThisCall(s); 

但是,如果你真的想存儲任何爲什麼不直接使用boost ::任何圖書館?

class VariantWithAny { 
    boost::any val; 
public: 
    VariantWithAny() {} 

    VariantWithAny(const boost::any& val) : val(val) {} 

    VariantWithAny& operator=(const boost::any& val) { 
     this->val = val; 
     return *this; 
    } 

    template <typename T> 
    void ThisCall(T* pThis) { 
     typedef void (T::* fptr)(); 
     fptr a = boost::any_cast<fptr>(val); 
     (pThis->*a)(); 
    } 
}; 

VariantWithAny myfunc2(&SomeClass::SomeMethod1); 
myfunc2 = &SomeClass::SomeMethod2; 
SomeClass* s2 = new SomeClass(); 
myfunc2.ThisCall(s2); 

boost :: any_cast是安全的,如果類型不匹配,將會拋出異常(boost :: bad_any_cast)。

[編輯]:boost :: any使用的技巧是將值存儲在繼承自純虛擬佔位符的模板持有者類中。這就是它幾乎可以持有任何價值,並且不必將它投入(void *)。查看實現 - 這是一個非常小的文件。

+0

爲什麼要編寫自己的變體,如果它只是打包現有的實現? –

+0

這只是一個匹配您使用的界面的包裝。如果你不需要這個調用方法,你可以直接使用boost :: any –