0

以前の質問に関連して、メンバー関数ポインターをいじっています。以下のコードでは、変数 (カウント) を変更するクラス (B) のメソッドを呼び出しますが、このクラスのインスタンスは作成しません。なぜこれが機能するのですか?

#include <iostream>
#include <string>
#include <map>

class A;
typedef int (A::*MEMFUNC)(int, int);

#define HANDLER(aclass, aproc) (MEMFUNC)(&aclass::aproc)

enum
{
    ADD=1,
    MUL,
    SUB,
    DIV
};

class B
{
    int count;
public:
    B() : count(0) {}
    ~B() {}
    int multiply(int x, int y) { count++; return x*y*count; }
    int divide(int x, int y) { count++; if (y!=0) return (x/y)*count; else return 0; }
};

class A
{
    std::map< int, MEMFUNC > funcs;
public:
    A() { AddLocals(); }
    ~A() {}
    int CallLocal(int nID, int x, int y)
    {
        MEMFUNC f = funcs[nID];
        if (f) return (this->*f)(x, y);
        else return 0;
    }
    void AddLocals()
    {
        Add(ADD, HANDLER(A, plus));
        Add(MUL, HANDLER(B, multiply));
        Add(SUB, HANDLER(A, subtract));
        Add(DIV, HANDLER(B, divide));
    }
    void Add(int nID, MEMFUNC f) { funcs[nID] = f; }
    int plus(int x, int y) { return x+y; }
    int subtract(int x, int y) { return x-y; }

};

int main()
{
    A aA;
    int a,b,c,d;

    a = aA.CallLocal(ADD,8,2);
    b = aA.CallLocal(MUL,8,2);
    c = aA.CallLocal(SUB,8,2);
    d = aA.CallLocal(DIV,8,2);

    std::cout << "a = " << a << "\n" 
              << "b = " << b << "\n" 
              << "c = " << c << "\n" 
              << "d = " << d << "\n";


    return 0;
}

(申し訳ありませんが、このメンバー関数ポインターは私を悩ませています)

4

4 に答える 4

8

マクロ defでのキャストHANDLERは、コンパイラに「黙れ! 自分が何をしているか分かっている!」と伝えます。

そのため、コンパイラはシャットダウンします。

まだ未定義の動作がありますが、UB の 1 つの特性は、場合によっては単純に期待すること、または実行してもらいたいことを実行することです。

しかし、そのようなコードがクラッシュしたり、クラッシュしたり、明らかにまったく関係のないコードが発生したり、不可解な不正な結果を引き起こしたりしても、驚かないでください。

または、たとえば、鼻の悪魔が鼻から飛び出します。

乾杯 & hth。

于 2010-10-19T11:10:58.307 に答える
1

結果は未定義の動作です。たとえば、私はそれを取得しb = 2083899728ますd = -552766888

操作している永続的なものは、Aのマップインスタンス内のintに相当するバイトである可能性があります(オブジェクトが実際にBである場合、それがcountメンバーが配置されるオフセットであるためです。

私のstdlib実装では、mapの最初のメンバーは比較関数であり、この場合はのインスタンスですstd::less<int>。サイズは1ですが、マップの他のメンバーを整列させるには、その後に未使用のパディングバイトが必要です。つまり、(少なくとも)このインスタンス化の最初の4バイトにはstd::map、何にも使用されていないガベージだけが含まれています(std :: lessにはデータメンバーがなく、状態を格納しません。マップ内のスペースが必要です) 。これは、コードがクラッシュしない理由を説明します。マップの機能に影響を与えないマップインスタンスの一部を変更しているのです。

count前にBにデータメンバーを追加するcount++と、マップの内部表現の重要な部分に影響し、クラッシュする可能性があります。

于 2010-10-19T12:06:32.367 に答える
1

C-castingを使用すると、あらゆる種類の恐ろしい振る舞いを回避できますが、それが問題ないという意味ではありません。

マクロを完全に取り除き、キャストしないでください。おそらく、boost::functionとboost::bindを使用して、実際に必要な動作を取得できます。

于 2010-10-19T11:33:14.210 に答える
1

あなたのコードは、クラス A のオブジェクトを使用してクラス B のメンバーを呼び出そうとすることにより、未定義の動作を呼び出しています。コンパイラがどのようにして観察した動作に到達できるかを説明することはできますが、同じ結果になるという保証はありません。何かを変更した場合の動作 (メンバーの追加/削除、コンパイラ設定の変更、または別のコンパイラの使用)。

マクロのキャストをHANDLER使用すると、互換性のない型の使用について警告するのではなく、指示どおりに実行するようにコンパイラに指示しています。この場合、任意のクラスのメンバーのアドレスをクラス A のメンバーのアドレスとして再解釈するようコンパイラーに指示します。

たとえば、後で を呼び出そうとするとB::multiply、その関数は、クラス B のオブジェクトで動作していないことを認識しないため、メンバーがオブジェクトであった場合にメンバーaAに対応するバイトを喜んで上書きします。ほとんどの場合、これらのバイトは実際には によって使用されていますが、明らかに重要なものではありません。クラス A を次のように変更した場合:B::countBA::funcs

class A
{
    int count;
    std::map< int, MEMFUNC > funcs;
public:
    A() : count(0) { AddLocals(); }
    ~A() {}
    int CallLocal(int nID, int x, int y)
    {
        MEMFUNC f = funcs[nID];
        if (f) return (this->*f)(x, y);
        else return 0;
    }
    int Count()
    {
        return count;
    }
    void AddLocals()
    {
        Add(ADD, HANDLER(A, plus));
        Add(MUL, HANDLER(B, multiply));
        Add(SUB, HANDLER(A, subtract));
        Add(DIV, HANDLER(B, divide));
    }
    void Add(int nID, MEMFUNC f) { funcs[nID] = f; }
    int plus(int x, int y) { return x+y; }
    int subtract(int x, int y) { return x-y; }

};

次に、さまざまな場所での結果を印刷するとaA.Count()、効果が示される場合があります。

非仮想メンバー関数であるため、コンパイラは予期された関数を呼び出しています。
非メンバー関数と非仮想メンバー関数の唯一の違いはthis、メンバー関数でポインターを供給する隠し引数にあります。したがって、非仮想メンバー関数のアドレスを取得すると、関数ごとに異なる固定アドレスが得られます。
メンバー関数が仮想関数であった場合、コンパイラは、その関数のポインターとして v テーブルにインデックスを返した可能性が最も高いでしょう (それが v テーブル オフセットであることを示す何らかの指示と共に)。次に、コードは、メンバー関数を直接呼び出すことができるかどうか、または関数が呼び出されるオブジェクトの v テーブルを介して間接的な呼び出しを行う必要があるかどうかを、呼び出しサイトで判断できます。

于 2010-10-19T12:47:06.653 に答える