4

インターフェイスを定義するライブラリを使用しています:

template<class desttype>
void connect(desttype* pclass, void (desttype::*pmemfun)());

そして私は小さな階層を持っています

class base {
   void foo();
};

class derived: public base { ... };

のメンバー関数でderived、呼び出したい

connect(this, &derived::foo);

しかし、実際には;&derived::fooのメンバ関数ポインタのようです。basegccが吐き出す

error: no matching function for call to ‘connect(derived* const&, void (base::* const&)())’

thisに明示的にキャストすることで、これを回避できますbase *。しかし、コンパイラが呼び出しを一致できないのはなぜですかdesttype = base(derived *暗黙的に にキャストできるためbase *)?

また、メンバー関数のポインターで ないのはなぜですか?&derived::fooderived

4

3 に答える 3

7

まず、&class::member結果の型は、メンバーが実際に宣言されたクラスに常に基づいています。これは、単項&が C++ でどのように機能するかです。

第 2 に、テンプレートの引数推定が失敗するため、コードがコンパイルされません。最初の引数から が導出さdesttype = derivedれ、2 番目の引数から が導出されdesttype = baseます。これがコンパイルが失敗する原因です。C++ のテンプレート引数推定規則は、型thisに変換できるという事実を考慮していませんbase *。さらに、型に変換する代わりに、this基本メンバーへのポインターから派生メンバーへのポインターbase *型に変換するのが適切な方法であると主張することができます。&derived::fooどちらのアプローチも同様に実行可能です (以下を参照)。

第 3 に、C++ のメンバー ポインターは反変性の規則に従いますこれは、基本クラス メンバーへのポインターを派生クラス メンバーへのポインターに暗黙的に変換できることを意味します。あなたの場合、引数を明示的に指定することにより、コンパイラがテンプレート引数の推定を通過できるようにするだけでよく、コードはコンパイルする必要があります

 connect<derived>(this, &derived::foo);

上記は、メンバーへのポインターであっても、ポインターの反分散のためにコンパイルする必要があります。あるいは、あなたがすることができます&derived::foobase

 connect<base>(this, &derived::foo);

ポインターの共分散のために、これもコンパイルする必要があります。this

実際の引数に明示的なキャストを使用して (質問で言及したように)、演繹のあいまいさを乗り越えることもできますが、私の意見では、この場合、明示的に指定されたテンプレート引数の方が見栄えがします。

于 2010-04-08T18:01:06.213 に答える
0

メンバー関数ポインターは、C++ に多くの特異性があり、さまざまなコンパイラーの動作に一貫性がありません。Doug Clugston の記事「Member Function Pointers and the Fastest possible C++ Delegates」は、それらがどのように機能するか (および機能しないか) の非常に優れた概要です。

派生クラスを扱う場合、いくつかの驚きがあります。たとえば、コメントをそのままにしておくと、次のコードは MSVC でコンパイルされます。

class SomeClass {
 public: 
    virtual void some_member_func(int x, char *p) {
       printf("In SomeClass"); };
};

class DerivedClass : public SomeClass {
 public:
 // If you uncomment the next line, the code at line (*) will fail!

//    virtual void some_member_func(int x, char *p) { printf("In DerivedClass"); };

};

int main() {
    // Declare a member function pointer for SomeClass

    typedef void (SomeClass::*SomeClassMFP)(int, char*);
    SomeClassMFP my_memfunc_ptr;
    my_memfunc_ptr = &DerivedClass::some_member_func; // ---- line (*)
}

興味深いことに、 &DerivedClass::some_member_funcは class のメンバ関数ポインタです SomeClass。のメンバーではありません DerivedClass! (一部のコンパイラは動作が若干異なります。たとえば、Digital Mars C++ の &DerivedClass::some_member_func場合、この場合は未定義です。)しかし、 DerivedClassoverrides some_member_funcの場合、コードはコンパイルされません 。&DerivedClass::some_member_funcDerivedClass

于 2010-04-08T18:54:57.887 に答える
0

これは、テンプレート引数推定の問題です。テンプレート引数が呼び出しサイトで明示的に記述されていない場合、コンパイラは自動変換を試みません。

私の経験では、これを回避する最善の方法は、関数の 2 つのテンプレート引数を宣言することです。

template<typename Y, typename T>
void connect(Y * pclass, void (T::*pmemfun)());

この場合、コンパイラは喜んで自動的にインスタンス化できます

void connect<derived, base>(derived * pclass, void (base::*pmemfun)());

派生 * からベース * への変換は接続内で行われるため、このソリューションも完全に安全です (ここで pclass->*pmemfun() を呼び出していると仮定します)。

于 2010-04-08T19:21:00.040 に答える