11

ここでは完全に間違っている可能性がありますが、私が理解しているように、C++ には実際にはネイティブの「メンバー関数へのポインター」型はありません。Boost や mem_fun などでトリックを実行できることは知っています。しかし、C++ の設計者は、たとえば、関数へのポインターとオブジェクトへのポインターを含む 64 ビット ポインターを使用しないことにしたのはなぜですか?

具体的には、型が不明な特定のオブジェクトのメンバ関数へのポインタです。IEコールバックに使用できるもの。これは、 2 つの値を含む型になります。最初の値は関数へのポインターであり、2 番目の値はオブジェクトの特定のインスタンスへのポインターです。

私が意味していないのは、クラスの一般的なメンバー関数へのポインターです。例えば

int (Fred::*)(char,float)

それはとても役に立ち、私の人生を楽にしてくれました。

ヒューゴ

4

8 に答える 8

17

@RocketMagnet - これは、重複とラベル付けされた他の質問への回答です。私はこの質問ではなく、その質問に答えています。

一般に、メンバー関数への C++ ポインターは、クラス階層全体で移植可能にキャストできません。そうは言っても、あなたはしばしばそれで逃げることができます. 例えば:

#include <iostream>
using std::cout;
class A { public: int x; };
class B { public: int y; };
class C : public B, public A { public: void foo(){ cout << "a.x == " << x << "\n";}};

int main() {
    typedef void (A::*pmf_t)();
    C c; c.x = 42; c.y = -1;

    pmf_t mf = static_cast<pmf_t>(&C::foo);
    (c.*mf)();
}

このコードをコンパイルすると、コンパイラは次のように文句を言います。

$ cl /EHsc /Zi /nologo pmf.cpp
pmf.cpp
pmf.cpp(15) : warning C4407: cast between different pointer to member representations, compiler may generate incorrect code

$

では、「なぜ C++ には void クラスのメンバ関数へのポインタがないのですか?」この架空のすべての基本クラスにはメンバーがないため、安全に割り当てることができる値はありません! 「void (C:: )()」と「void (void:: )()」は相互に互換性のない型です。

さて、あなたは「待って、以前にメンバー関数ポインターをうまくキャストしたことがある!」と考えているに違いありません。はい、reinterpret_cast と単一継承を使用している可能性があります。これは、他の再解釈キャストと同じカテゴリにあります。

#include <iostream>
using std::cout;
class A { public: int x; };
class B { public: int y; };
class C : public B, public A { public: void foo(){ cout << "a.x == " << x << "\n";}};
class D { public: int z; };

int main() {
    C c; c.x = 42; c.y = -1;

    // this will print -1
    D& d = reinterpret_cast<D&>(c);
    cout << "d.z == " << d.z << "\n";
}

したがって、存在する場合でもvoid (void::*)()、安全に/移植可能に割り当てることができるものは何もありません。

メンバー関数ポインターは継承階層の上下に適切にキャストされませんが、void ポインターは適切にキャストされるためですvoid (*)(void*)void (void::*)()その代わり:

#include <iostream>
using std::cout;
class A { public: int x; };
class B { public: int y; };
class C : public B, public A { public: void foo(){ cout << "a.x == " << x << "\n";}};

void do_foo(void* ptrToC){
    C* c = static_cast<C*>(ptrToC);
    c->foo();
}

int main() {
    typedef void (*pf_t)(void*);
    C c; c.x = 42; c.y = -1;

    pf_t f = do_foo;
    f(&c);
}

それであなたの質問に。C++ がこの種のキャストをサポートしないのはなぜですか。メンバー関数へのポインター型は、既に仮想対非仮想基本クラス、および仮想対非仮想メンバー関数をすべて同じ型で処理する必要があり、一部のプラットフォームではそれらを 4*sizeof(void*) に膨張させます。メンバー関数へのポインターの実装がさらに複雑になり、生の関数ポインターはすでにこの問題をうまく解決しているためだと思います。

他の人がコメントしたように、C++ はライブラリ ライターにこれを行うのに十分なツールを提供します。その後、あなたや私のような「通常の」プログラマは、これらの詳細に汗をかくのではなく、これらのライブラリを使用する必要があります。

編集: マークされたコミュニティ wiki。C++ 標準への関連する参照を含めるように編集し、斜体で追加してください。(特に、私の理解が間違っていた標準への参照を追加してください! ^_^ )

于 2009-01-20T19:29:43.203 に答える
12

他の人が指摘したように、C++ にはメンバー関数ポインター型があります。

あなたが探していた用語は「バインドされた関数」です。C++ が関数バインド用のシンタックス シュガーを提供しない理由は、最も基本的なツールのみを提供し、それを使用して必要なものをすべて構築できるという哲学のためです。これは、言語を「小さく」保つのに役立ちます (少なくとも、気が遠くなるような巨大さを軽減します)。

同様に、C++ には C# のような lock{} プリミティブはありませんが、boost の scoped_lock で使用される RAII があります。

もちろん、役に立つ可能性のあるすべてのものに構文糖衣を追加する必要があるという考え方もあります。良くも悪くも、C++ はその流派に属していません。

于 2009-01-20T17:18:50.863 に答える
3

します。

例えば、

int (Fred::*)(char,float)

Fredを返し、intおよび を受け取るクラスのメンバ関数へのポインタcharですfloat

于 2009-01-20T17:11:43.763 に答える
1

問題は確かに、オブジェクトポインタと関数ポインタを1つの使いやすいパッケージに含めることの基本ではありません。これは、ポインタとサンクを使用して行うことができるためです。(このようなサンクは、仮想メンバー関数へのポインターをサポートするためにx86上のVC ++によって既に使用されているため、これらのポインターは4バイトしか使用しません。)多くのサンクが発生する可能性がありますが、実際には、人々はすでにリンカーに依存しています。重複するテンプレートのインスタンス化を排除します(とにかくそうです)。vtableは非常に多く、実際にはこのオフセットが発生します。適度なサイズのプログラムの場合、オーバーヘッドはおそらく重要ではありません。このようなものを使用しない場合は、費用はかかりません。

(従来、TOCを使用していたアーキテクチャーでは、TOCポインターは、既に実行する必要があるのと同じように、サンクではなく、関数ポインター部分に格納されます。)

(もちろん、この新しいタイプのオブジェクトは、サイズが異なるため、通常のポインターが機能するように正確に置き換えることはできません。ただし、呼び出しポイントでは同じように書き込まれます。)

私が見ている問題は、呼び出し規約の問題です。生成されたコードは、実際のタイプに関係なく同じ方法で引数(これを含む)を準備する必要があるため、この方法で関数へのポインターをサポートすることは、一般的な場合には注意が必要です。ポインタが指すもの、関数、またはメンバー関数。

これはおそらくx86では大したことではありません。少なくともthiscallでは、ECXをロードするだけで、呼び出し元の関数がそれを必要としない場合は偽物になることを受け入れることができるからです。(そして、VC ++は、この場合、ECXは偽物であると想定しています。)しかし、レジスター内の名前付きパラメーターの引数を関数に渡すアーキテクチャーでは、サンクでかなりの量のシャッフルが発生する可能性があり、スタック引数が左にプッシュされると-右に、あなたは基本的に詰め込まれています。そして、これは静的に修正することはできません。これは、制限内に相互翻訳ユニット情報がないためです。

[編集:MSaltersは、上記のrocketmagnetの投稿へのコメントで、オブジェクトと関数の両方がわかっている場合、このオフセットなどをすぐに決定できると指摘しています。これは私にはまったく起こりませんでした!しかし、これを念頭に置いて、正確なオブジェクトポインター、おそらくオフセット、および正確な関数ポインターのみを格納する必要があると思います。これにより、サンクは完全に不要になります-私は思います-しかし、メンバー関数と非メンバー関数を同様に指す問題は残ると確信しています。]

于 2009-01-21T01:35:40.207 に答える
1

TR1 には std::tr1::function があり、C++0x に追加されます。だから、ある意味でそれを持っています。

C++ の設計哲学の 1 つは、「使用しないものには料金を支払わない」というものです。C# スタイルのデリゲートの問題は、それらが重く、言語サポートが必要であり、使用するかどうかに関係なく、誰もが支払う必要があることです。そのため、ライブラリの実装が推奨されます。

デリゲートが重い理由は、メソッド ポインターが通常のポインターよりも大きいことが多いためです。これは、メソッドが仮想の場合に発生します。メソッド ポインターは、それを使用する基本クラスに応じて異なる関数を呼び出します。これには、vtable とオフセットの少なくとも 2 つのポインターが必要です。メソッドに関連する他の奇妙さは、多重継承に関与するクラスからのものです。

とは言っても、私はコンパイラ ライターではありません。参照されるメソッドの仮想性を覆す、バインドされたメソッド ポインターの新しい型を作成することができた可能性があります (メソッドがバインドされている場合、基本クラスが何であるかがわかっているため)。

于 2009-01-20T18:41:18.077 に答える
-1

メソッドには暗黙のthis引数があるので、cメソッドへのポインタはメソッドを呼び出すには不十分です(どのインスタンスを使用するかを決定する方法がないためthis(またはインスタンスが現在存在している場合でも)) )。

編集: Rocketmagnetは、質問でこれに対処したとコメントしています。これは事実のようですが、この応答を開始した後に追加されたと思います。とにかく「meaculpa」と言います。

それで、私が考えを少し拡張させてください。

C ++はcと密接に関連しており、そのすべての固有の型は以前の言語と互換性があります(主にc++開発の歴史のためだと思います)。したがって、組み込みc++ポインターはcポインターであり、要求された使用をサポートすることはできません。

確かに、ブーストの実装のように、その仕事をするために派生型を構築することはできますが、そのような生き物はライブラリに属しています。

于 2009-01-20T18:57:23.567 に答える