3

メソッド ポインターを作成できること、およびそれらが一般的に関数ポインターとは異なることを知っています。それらの間の変換は許可されていません。メソッド ポインターには、ポインターの調整方法などに関する多くのデータが含まれる場合がありますthis

特定のメソッドを実行しているコードの実際のアドレスを取得する方法を知りたいです。このアドレスはいかなる方法でも延期されませんが、特定のメソッドが属する型の識別子として使用されます - RTTI に少し似ていますが、このコードは通常 1 つのユニットでしか利用できないため、ライブラリの境界に影響されないという利点があります。 .

編集:

getType()オブジェクトの型を返すメソッドを追加しないのはなぜですか?

自分で作成したクラスだけでなく、このように使えるようにしたいからです。私がやろうとしているのは、基本的にvariantクラスの実装です。これは、基本的にすべてを受け入れ、特定のタイプの void* getVariantId() の特殊化が存在する場合に提供されます。

だから私は書くことができます:

template <typename T>
class getTypeId{
};

class foo{
    public:
    void bar();
};

// getTypeId declared but not defined for generic type T earlier...
template <>
class getTypeId<foo>{
    static void* get<foo>(){
        return &foo::bar;
    }
};

void doSomething(myVariant var);

int main(int argc, char* argv[]){
     foo f;
     myVariant var = myVariant::fromType<foo*>(&f);
     doSomething(f);
}

void doSomething(myVariant var){
    foo* f = var.toType<foo*>(); // Returns foo object from main
    int* i = var.toType<int*>(); // returns null, as var is not int* in my example.
}

fromTypeを使用getTypeIdして型を表す値を取得し、 にキャストされたオブジェクトのポインタとともにそれを強化するという考え方ですvoid*toType一方、`getTypeId::get から取得した値とオブジェクトに格納された値を比較します。一致する場合、内部で保持されているオブジェクト ポインターが元の型に再解釈されて返されます。

このソリューションの優れた点は、タイプ x を定義する共有ライブラリ X がある場合でも、ライブラリ X を個別に使用するライブラリ Y と Z がタイプ x が同じであることに同意することです (たとえば、Y でバリアントを作成する場合)。 Z に渡される)、X::method の原因アドレスは同じままです。Y または Z ライブラリ (ただし X ではありません!!!) の作成者として、X ライブラリで RTTI が有効になっていることを確認する必要はありません。

EDIT2:

これを実現するための実装に依存しない方法はないことがわかりました。ただし、これは作成できない機能ではなく、通常は必要ないため、存在しない機能の癖だと思います。何を達成したかったのか、どのように計画していたのか、まだ明確になっていないと思います。そう:

  • ライブラリとは、ここでは共有ライブラリ (.dll、.so など) を意味します。

  • 純粋仮想ではないすべてのメソッド (定義されているすべてのメソッドを意味します) には、実装が必要です。この実装は、1 つの追加パラメーターを受け入れる関数として動作しますthis。仮想関数は、呼び出し元側でのみ異なります。X::fooクラス X とそのメソッドを例に取りましょう。このメソッドは X 型にのみバインドされ、Y 型が継承してもこのメソッドは残るX::fooため、型を識別するには十分Xです。仮想メソッドのオーバーライドまたは子クラスのメソッドのカバーXchildは、事実上、新しいメソッド Xchild::foo を新しいアドレスで定義することです。次に、Xchild::fooを表すために使用できますがXchild、 ではありませんX

  • タイプ X (正確にはすべてのメソッド) がライブラリ libX で定義されており、libY と libZ の両方が libX を使用しており (ただし、お互いを認識していない)、X の最初のメソッドを使用して独自の目的で getTypeId を定義している場合、それを表します (これは libX 開発者に何も課さないことに注意してください)、彼らは X 型が何であるかについて合意しています。アプリケーション開発者が libY を使用してバリアントを取得し、それを libZ に渡す場合、両方が言及されたタイプを適切に認識します。

  • 私は type-casting-variant を開発しようとはしていません - myVariant に渡された実際のポインタが Xchild だったとしても、X として保存されたバリアントは X としてのみ読み取ることができます。特定のインスタンスの最上位クラスのタイプを取得することに興味はありません-バリアントの作成に使用されたクラスだけです。

4

3 に答える 3

3

C++ FAQ Lite では、セクション全体をPointers to Member Functionsに当てています。

特定の2つの答えは、あなたがやろうとしていることを実行できない理由を述べています(少なくとも、移植可能な方法ではありません):

  • メンバー関数へのポインターは別の動物です。

C++ では、メンバーへのポインターと呼ばれる新しいタイプのポインターが導入されています。これは、オブジェクトを提供することによってのみ呼び出すことができます。メンバー関数へのポインターを関数へのポインターに「キャスト」しようとしないでください。結果は未定義であり、おそらく破滅的です。たとえば、メンバ関数へのポインタは、適切な関数のマシン アドレスを含む必要はありません。

  • メンバー関数へのポインターは、仮想関数を指すことができます。

メンバー関数へのポインターは、単一のポインターではなく、データ構造である可能性があります。考えてみてください。仮想関数を指している場合、実際には静的に解決可能なコードの山を指していない可能性があるため、通常のアドレスでさえない可能性があります。

于 2011-11-07T22:41:39.510 に答える
1

特定のメソッドを実行しているコードの実際のアドレスを取得する方法を知りたいです。このアドレスはいかなる方法でも延期されませんが、特定のメソッドが属する型の識別子として使用されます - RTTI に少し似ていますが、このコードは通常 1 つのユニットでしか利用できないため、ライブラリの境界に影響されないという利点があります。 .

RTTI は翻訳単位によって制限されません。ライブラリに関する RTTI の唯一の制限は、1 つの DLL が派生クラスを返す関数を公開する DLL 境界です。100% 確信があるわけではありませんが、DLL と実行可能ファイルがすべて同じコンパイラでビルドされている (そして静的ライブラリを共有している) 限り、それでも動作すると思います。

いずれにせよ、あなたが求めていることはできません。メンバー ポインターはポインターではなく、メンバー関数への実際の関数ポインターを取得する移植可能な方法はありません。

できたとしても、型の一意の識別子は得られません。結局のところ、クラスが関数をオーバーライドしない場合、または関数が仮想関数でない場合、そのメンバー関数のアドレスは、それを使用する各クラスで同じになります。関数が仮想で、クラスがそれをオーバーライドする場合にのみ変更されます。

そこから一意の型識別子を作成する方法はありません。

あなたが望むのはBoost.Anyで、内部で RTTI を使用して間違った型にキャストするのを防ぎます。


計画が崩れる場所

getTypeId次のように実装される関数を提案しています。

template<typename T>
SomeIdentifierTypedef getTypeId(const T &x)
{
  void *ptr = GetMemberFuncPtr<T>(x, &x::foo);
  return FindIdFromPtr(ptr);
}

GetMemberFuncPtrこれには、メンバ ポインタとオブジェクトを取り、そのメンバの実際の関数ポインタを取得するの存在が必要です。この関数は、受け取るすべての型に対して一意の値を返すことを目的としています。

さて、あなたが言ったことを考えてみましょう:

クラス X を取り上げてみましょう。それはメソッド X::foo です。このメソッドは X 型にのみバインドされ、Y 型が継承しても、このメソッドは X::foo のままであり、X 型を識別するには十分です。

Y::fooとが同じメンバー関数である場合X::foo、当然、それらは同じ関数ポインターを持ちます。したがって、getTypeId(Y()) == getTypeId(X()). それは の定められた目的に違反しgetTypeIdます。タイプが Y の場合、X の場合とは異なる値を返すようにします。

タイプ X (正確にはすべてのメソッド) がライブラリ libX で定義されており、libY と libZ の両方が libX を使用しており (ただし、お互いを認識していない)、独自の目的で getTypeId を X の最初のメソッドを使用して定義している場合、それを表します (これは libX 開発者に何も課さないことに注意してください)、彼らは型 X が何であるかについて合意しています。アプリケーション開発者が libY を使用してバリアントを取得し、それを libZ に渡す場合、両方が言及されたタイプを適切に認識します。

これは 2 つの理由で機能しません。getTypeIdまず、私の実装が含まれていることに注意し::fooてください。これは、クラスの任意のメソッドについて話す方法がないためです。特定の関数について話すには、名前 (および確実に知りたい場合は本格的な署名) を使用する必要があります。「この型から関数を取得しください」とだけ言うことはできません。いずれかを選択する必要があります。

つまり、可能なすべてのテンプレート タイプに対して同じgetTypeIdものを選択する必要があります。したがって、使用するすべてのクラスは同じ関数を実装するgetTypeId必要があります。

ああ、特定のタイプに特化してみるのもいいかもしれません。しかし、それは 2 番目の問題に真っ向からぶつかります。

共有ライブラリは、明示的に共有されない限り、関数を共有しません。を共有することはできませんX::foo完全で個別の定義がないクラスをlibXが渡す唯一の方法は、そのクラスが何らかの方法で隠されている場合です。したがって、それは不透明なポインター ( ) であり、その場合、メンバー関数を取得することさえできません。または、 と の両方を持つ定義からの派生クラスです。libYlibYvoid*libXlibY

つまり、 と の両方に対してコンパイルされた (おそらく抽象) クラスがbaseあります。から派生した を作成し、を として返す を公開する関数がいくつかあります。libXlibYlibXXbaselibXXbase *

libXこの場合、特殊化にしかアクセスできないため、特殊化は役に立ちませんgetTypeId<X>。それが機能する唯一の方法は、専門化がgetTypeId<base>. しかし、ユーザーが仮想関数をオーバーライドしないとどうなるかという問題に戻ります。次に、それはまだだと思いますbase

テスト関数が純粋仮想関数であっても、それは役に立ちません。これは、が から派生し、がから派生するlibX可能性があるためです。関数を実装している可能性がありますが、オーバーライドしていません。だからまた。YXbaseXYgetTypeId(x) == getTypeId(y)

あなたのアイデアは機能しません。

于 2011-11-07T22:04:28.730 に答える