5

他のコードで使用される基底クラスの引数を受け取るメンバー関数ポインターを使用する必要があります。ええと、単純に、下の例のような[何か]をしたいのです。このコードは正常に動作しますが、そのようなキャストは常に安全なのだろうか? dynamicここで行うこともstaticキャストすることもできません。

#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;                                                   
}                                                              
4

4 に答える 4

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 );

最終的には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&)再帰的に自分自身を呼び出すことになります。

于 2011-05-31T19:16:35.663 に答える
1

いいえ、あなたの例はうまく機能しません。まず、関連するクラスタイプ間でのキャストに
のみ使用でき、他のものには使用できません。次に、それをまたはCスタイルのキャスト(私が意図したと思います)に 置き換えても、次の出力が得られます。dynamic_cast
dynamic_castreinterpret_cast

c
c

本当にあなたが望んでいたものではありません。

それが機能し、ひどくクラッシュしない理由は、member-function-pointers間を行き来することが「安全」であるため、情報が失われることはありません。
それでも何かを出力するのは、コンパイラが型のエラーを認識しないためですが、アセンブリは型を考慮せず、アドレスのみを考慮します。そのため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):╠</ p>

はい、行きます。C::cはオフセット+4+0+ sizeof int)にありprintf、そこから読み取ります。ではD、そのようなオフセットはなくprintf、初期化されていないメモリから読み取ります。一方、初期化されていないメモリへのアクセスは、未定義の動作です。

それで、最終的に結論に達するために:いいえ、これは安全ではありません。:)

于 2011-05-31T19:02:00.143 に答える
1

コンパイラは、作成した dynamic_cast を含むコードを拒否する必要があります。(タイプミスだと思います。紹介テキストを考慮して、reinterpret_castを意味していました)。

reinterpret_cast を使用すると、明確に定義されたケースの 1 つにはなりません (ほとんどの場合、別の型に変換してから元の型に戻す必要があります)。したがって、キャストの結果については未指定の領域にあり、キャストの結果を呼び出すときの動作については未定義の領域にいます。

于 2011-05-31T19:00:25.993 に答える
0

それを機能させるには、 a を使用する必要がありますreinterpret_cast。この場合は安全なはずですが (備考を参照)、多重継承を使用すると失敗する可能性があります。これは、 a を aDとして渡すときにポインターを調整する必要があるためCです。コンパイラは、これが発生する必要があることを知る必要がありますが、この場合は不可能です ( pgadを使用して呼び出すと、このステップがスキップされ、メンバー関数はDオブジェクトの変更されていないアドレスを取得します)。

備考: 私は安全だと言いました- まあ、実際には、型を無関係な型に reinterpret_cast し、その型を使用するため、未定義の動作ですが、それでもほとんどのコンパイラで動作するはずです。プロダクション コードでは使用しないでください。

于 2011-05-31T18:57:11.317 に答える