12

私の質問は、基本クラスのコンストラクターから仮想メンバー関数を呼び出すことではなく、仮想メンバー関数へのポインターが基本クラスのコンストラクターで有効かどうかです。

以下を考えると

class A
{
    void (A::*m_pMember)();

public:
    A() :
        m_pMember(&A::vmember)
    {
    }

    virtual void vmember()
    {
        printf("In A::vmember()\n");
    }

    void test()
    {
        (this->*m_pMember)();
    }
};

class B : public A
{
public:
    virtual void vmember()
    {
        printf("In B::vmember()\n");
    }
};

int main()
{
    B b;
    b.test();

    return 0;
}

これにより、準拠しているすべての C++ コンパイラに対して "In B::vmember()" が生成されますか?

4

6 に答える 6

3

「有効」は、ポインターに適用される特定の用語です。NULLデータ ポインタは、オブジェクトまたは;を指している場合に有効です。関数ポインターは、関数 またはNULLを指している場合に有効であり、メンバーへのポインターは、メンバー または を指している場合に有効ですNULL

ただし、実際の出力に関するご質問から、別のことをお聞きになりたいと推測できます。あなたのvmember関数を見てみましょう - それとも関数と言うべきですか? 明らかに、2 つの関数本体があります。派生した 1 つだけを仮想にすることもできたので、実際には 2 つのvmember関数があり、どちらもたまたま仮想であることが確認されます。

ここで、メンバー関数のアドレスを取得するときに、実際の関数が既に選択されているかどうかが問題になります。あなたの実装はそうではないことを示しており、これはポインターが実際に逆参照された場合にのみ発生します。

このように動作しなければならない理由は簡単です。メンバー関数のアドレスの取得には、仮想呼び出しを解決するために必要な実際のオブジェクトは含まれません。披露させて:

namespace {
  void (A::*test)() = &A::vmember;
  A a;
  B b;
  (a.*test)();
  (b.*test)();
}

を初期化するtestと、タイプのオブジェクトはまったくないAB、まったくありませんが、 のアドレスを取得することは可能です&A::vmember。その同じメンバー ポインターを 2 つの異なるオブジェクトで使用できます。これは、「In A::vmember()\n」と「In B::vmember()\n」以外に何を生成できますか?

于 2010-06-22T14:00:47.713 に答える
3

ポインターは有効ですが、仮想関数がポインターを介して呼び出される場合、左側で使用されるオブジェクトの動的な型に従って常に解決されることに注意する必要があります。これは、コンストラクターから仮想関数を呼び出す場合、それを直接呼び出すか、ポインターを介して呼び出すかは問題ではないことを意味します。どちらの場合も、呼び出しはコンストラクターが現在動作している型に解決されます。これが、オブジェクトの構築 (または破棄) 中に仮想関数を呼び出すときに、仮想関数がどのように機能するかです。

また、メンバー関数へのポインターは、通常、初期化の時点で特定の関数にアタッチされないことに注意してください。ターゲット関数が非仮想の場合、ポインターは特定の関数を指していると言えます。ただし、ターゲット関数が仮想の場合、ポインターが指している場所を知る方法はありません。たとえば、言語仕様では、たまたま仮想関数を指している 2 つのポインターを (等しいかどうか) 比較すると、結果はunspecifiedになると明示的に述べられています。

于 2010-06-22T18:05:35.690 に答える
1

メンバー関数ポインターとその使用方法の詳細については、この記事をお読みください。これはあなたのすべての質問に答えるはずです。

于 2010-06-22T12:00:12.010 に答える
1

Old New Thing (Microsoft の Chuck Norris と呼ばれることもある Raymond Chen によるブログ)についての簡単な説明を見つけました。

もちろん、コンプライアンスについては何も述べていませんが、その理由を説明しています。

B b;

b.A::vmember(); // [1]

(b.*&A::vmember)(); // [2]

1 と 2 は実際には別の関数を呼び出します...これは本当に驚くべきことです。また、メンバー関数へのポインターを使用してランタイムディスパッチを実際に防ぐことはできないことも意味します:/

于 2010-06-22T17:46:39.293 に答える
0

違うと思う。仮想メンバー関数へのポインターはVMTを介して解決されるため、この関数の呼び出しと同じ方法で行われます。これは、コンストラクターの終了後にVMTにデータが入力されるため、無効であることを意味します。

于 2010-06-22T11:59:55.380 に答える
-1

IMOimplementation definedは、仮想機能のアドレスを取得することです。これは、コンパイラの実装に固有のvtableを使用して仮想関数が実装されるためです。vtable は、クラス ctor の実行が完了するまで完全であることが保証されないため、そのようなテーブル (仮想関数) 内のエントリへのポインターはimplementation defined behavior.

SO here数か月前に私が尋ねた、やや関連する質問があります。基本的に、仮想関数のアドレスの取得はC++標準では指定されていません。

したがって、たとえそれがうまくいったとしても、ソリューションは移植可能ではありません。

于 2010-06-22T12:35:50.577 に答える