5

我需要使用一個成員函數指針,它接受在其他代碼中使用的基類參數。那麼,簡單地說,我想要做的[某事]像下面的例子。此代碼工作正常,但我不知道這樣的演員總是安全嗎?我不能在這裏施放dynamicstatic鑄造成員函數指針

#include <cstdio>             

class C 
{               
public:                
     C() : c('c') {}            
     virtual ~C() {}            

     const char c;            
};                 

class D : public C 
{             
public:                
     D() : d('d') {}            
     virtual ~D() {}            

     const char d;            
};                 

class A 
{               
public:                
     A() {}              
     virtual ~A() {}            

     void f(C& c) { printf("%c\n",c.c); }      
     void g(D& d) { printf("%c %c\n",d.c,d.d); }    
};                 

int main (int argc, char const* argv[])        
{                 
     void (A::*pf)(C& c) = &A::f;        
     void (A::*pg)(D& d) = reinterpret_cast<void (A::*)(D&)>(&A::f); 

     A a;               
     C c;               
     D d;               

     (a.*pf)(c);            
     (a.*pg)(d);            

     return 0;             
}                
+3

你說的代碼工作正常,但它甚至不爲我編譯。 – Xeo 2011-05-31 18:48:47

+1

它編譯和(似乎)與'reinterpret_cast'一起工作;我想問題是,這是安全的嗎? – 2011-05-31 18:51:35

+0

感覺f和g實際上是虛擬的嗎?如果是這樣的話,你可以在一些從A派生的類中重寫它們。比鑄造好得多。 – 2011-05-31 19:15:22

回答

2

你想要做的事情不能在C++中合法地完成。不管這是成員函數還是自由函數,C++都不支持函數參數類型的任何類型的協方差或對數方差。

在您的情況來實現它的正確方法是引入了參數類型轉換目的的中間功能

class A 
{               
public:   
    ...             
    void f(C& c) { printf("%c\n",c.c); }      
    void f_with_D(D& d) { f(d); } 
    ... 
};   

,讓你的指針指向的是中間的功能沒有任何強制轉換

void (A::*pg)(D& d) = &A::f_with_D; 

現在

A a; 
D d;               
(a.*pg)(d); 

最終將CA將a.fC對象的子對象d作爲參數。

編輯:是的,它將與功能過載(如果我正確理解你的問題)。你一定要記住,以便直接向函數的正確版本的內部通話功能超載,你必須使用顯式類型轉換

class A 
{               
public:   
    ...             
    void f(C& c) { printf("%c\n",c.c); }      
    void f(D& d) { f(static_cast<C&>(d)); } 
    ... 
};   

沒有你結了投A::f(D&)遞歸調用自己。

+0

不幸的是,有很多Ds類,我希望編譯器選擇適當的類。它會與功能超負荷工作? – qba 2011-05-31 21:10:31

+0

不,不會。所以結論是我需要完全不同的解決方案。 – qba 2011-05-31 21:30:00

+0

它的工作原理,謝謝。目前只有在測試代碼不生產一個,但有一個希望:)在生產代碼中,我得到'錯誤:無效static_cast從類型'<無法解析的重載函數類型>''。也許還有別的東西搞亂了(static_cast是由庫完成的)。 – qba 2011-06-01 06:37:46

0

您需要使用reinterpret_cast才能使其工作。在這種情況下它應該是安全的(請參見備註),但如果使用多重繼承,則可能會失敗,因爲在將D作爲C傳遞時,需要調整指針。編譯器需要知道這是必須發生的,在這種情況下這是不可能的(調用pgd只會跳過這一步,成員函數會得到D對象的未修改地址)。

備註:我說安全 - 嗯,實際上它是不確定的行爲由於reinterpret_cast'ing一個類型無關的類型和使用該類型,但它應該在大多數編譯器仍然工作。請不要在生產代碼中使用它。

1

編譯器應該用你寫的dynamic_cast來拒絕代碼。 (我認爲這是一個錯字,考慮到你的介紹文本,你的意思是reinterpret_cast)。

隨着reinterpret_cast,你不在一個明確定義的情況下(主要涉及到轉換爲另一種類型,然後回到原來的情況)。所以我們處於未指定領域的演員結果,以及在調用演員結果時的未定義領域。

1

不,您的示例無法正常工作。
首先,您只能使用dynamic_cast相關類別類型之間進行投射,而不是其他。
其次,即使你更換dynamic_castreinterpret_cast或C樣式轉換(我假設你的意思),然後我得到以下輸出:

c
c

沒有真正想要的東西。

爲什麼即使有效,也不會發生可怕的崩潰是因爲在成員函數指針之間來回轉換是「安全」的,所以不會丟失任何信息。
爲什麼它仍然打印一些東西是因爲編譯器沒有發現類型錯誤,但程序集不關心類型,它只關心地址,所以它仍然會調用A::f,因爲這是您保存的指針,無論的類型。

有趣的是,即使您沒有關聯類(D不會繼承自C),這仍然有效,因爲程序集不關心類型。改變A中的函數以下列方式:

void f(C& c) { printf("f(C& c): %c\n",c.c); } 
void g(D& d) { printf("g(D& d): %c\n",d.d); } 

導致以下的輸出:

f(C& c): c
f(C& c): d

「?如何運作的D甚至沒有一個c成員!」。那麼,再次因爲地址。兩個變量都與this指針相同,即+0。現在,讓我們把另一成員C(類簡化):

struct C{ 
     C() : c('c') {} 
     int i; // mean 
     const char c; 
}; 

,並再次嘗試的是,輸出:

f(C& c): c
f(C& c): ╠

是的,我們走吧。 C::c現在在偏移量+4+0 + sizeof int)和printf從那裏讀取。在D中,沒有這樣的偏移量,並且從未初始化的存儲器讀取printf。另一方面,訪問未初始化的內存是未定義的行爲

因此,最後得出結論:不,這是不安全的。 :)

+0

好吧,reinterpret_cast的結果是我真正想要的。 pg是函數f的指針,所以它應該調用函數f。它還能是什麼? – qba 2011-05-31 21:05:32

+0

@qba:那你爲什麼稱它爲'pg'並且在'A'中有一個'g'功能?如果你想調用'f'函數,爲什麼在你的代碼中包含'g'? – Xeo 2011-05-31 21:12:33

+0

另一個例子複製和粘貼有多糟糕:P我正在用這些函數測試C++,並且我忘記刪除'g'。 – qba 2011-05-31 21:26:53