13

それ自体へのポインターからそれ自体を含むオブジェクトへのポインターを計算できるメンバー変数を持つことは可能ですか?

次のように API でラップされた外部呼び出しインターフェイスを用意しましょう。

template <typename Class, MethodId Id, typename Signature>
class MethodProxy;

template <typename Class, MethodId Id, typename ReturnT, typename Arg1T>
class MethodProxy<Class, Id, ReturnT ()(Arg1T) {
  public:
    ReturnT operator()(Class &invocant, Arg1T arg1);
};

0 から N までの他の数の引数についても同様です。外部側の各クラスでは、1 つの C++ クラスがいくつかのトレイトで宣言され、このテンプレートはそれらのトレイト (および引数の型のその他のトレイト) を使用して、外部メソッドを見つけて呼び出します。これは次のように使用できます。

Foo foo;
MethodProxy<Foo, barId, void ()(int)> bar;
bar(foo, 5);

今私がやりたいのはFoo、次のように呼び出すことができるような方法で定義することです:

Foo foo;
foo.bar(5);

署名を何度も繰り返さずに。(明らかに、静的メンバーを作成し、呼び出しをメソッドにラップするのは簡単です)。実際、それはまだ簡単です。

template <typename Class, MethodId Id, typename Signature>
class MethodMember;
template <typename Class, MethodId Id, typename ReturnT, typename Arg1T>
class MethodMember<Class, Id, ReturnT ()(Arg1T) {
    MethodProxy<Class, Id, Signature> method;
    Class &owner;
  public:
    MethodMember(Class &owner) : owner(owner) {}
    ReturnT operator()(Arg1T arg1) { return method(owner, arg1); }
};

ただし、それは、オブジェクトがそれ自体へのポインタの多くのコピーを含むことになることを意味します。thisしたがって、これらのインスタンスが所有者ポインターといくつかの追加のテンプレート引数を計算できるようにする方法を探しています。

の方針に沿って考えていた

template <typename Class, size_t Offset, ...>
class Member {
    Class *owner() {
        return reinterpret_cast<Class *>(
            reinterpret_cast<char *>(this) - Offset);
    }
    ...
};
class Foo {
    Member<Foo, offsetof(Foo, member), ...> member;
    ...
};

しかし、これは Foo がその時点で不完全な型であると不平を言っています。

はい、offsetof「POD」タイプでのみ機能するはずですが、実際には非仮想メンバーで機能します。同様に、その引数に (ダミーの基本クラスを使用して) メンバーへのポインターを渡そうとしましたが、それも機能しません。

これが機能する場合は、含まれているクラスのメソッドに委譲する C# のようなプロパティを実装するためにも使用できることに注意してください。

上記のラッパー メソッドを boost.preprocessor で実行する方法は知っていますが、引数リストは奇妙な形式で指定する必要があります。テンプレートを介して一般的なラッパーを生成するマクロを作成する方法は知っていますが、それでは診断が不十分になる可能性があります。呼び出しが次のように見える場合も簡単ですfoo.bar()(5)。しかし、いくつかの巧妙なトリックが可能かどうかを知りたいです (さらに、そのような巧妙なトリックのみがプロパティにも使用できる可能性があります)。

注: オフセットを割り当てる前に型を認識しておく必要があるため、メンバー型は、それへのメンバー ポインターまたはそのオフセットのいずれかで実際に特殊化することはできません。これは、型が必要なアラインメントに影響を与える可能性があるためです (明示的/部分的な特殊化を検討してください)。

4

4 に答える 4

9

質問をすることは答えを理解するための最良の方法です。

オフセットを計算する前に型を知る必要があるため、オフセットをテンプレート引数にすることはできません。したがって、引数の関数によって返される必要があります。タグ タイプ (ダミー構造体) を追加し、オーバーロードされた関数を Owner に配置するか、タグに直接配置します。そうすれば、必要なものすべてを 1 か所で (マクロを使用して) 定義できます。次のコードは、gcc 4.4.5 で正常にコンパイルされ、すべてのメンバーの正しいポインターを出力します。

#include <cstddef>
#include <iostream>

using namespace std;

(実際にコンパイルするためのプリアンブルのみ)

template <typename Owner, typename Tag>
struct offset_aware
{
    Owner *owner()
    {
        return reinterpret_cast<Owner *>(
            reinterpret_cast<char *>(this) - Tag::offset());
    }
};

これは、オブジェクトが自身のオフセットを認識できるようにするために必要なものです。プロパティやファンクター、その他のコードを自由に追加して、便利にすることができます。ここで、メンバー自体と一緒に追加のものを宣言する必要があるため、このマクロを定義しましょう。

#define OFFSET_AWARE(Owner, name) \
    struct name ## _tag { \
        static ptrdiff_t offset() { \
            return offsetof(Owner, name); \
        } \
    }; \
    offset_aware<Owner, name ## _tag> name

これは、構造体をタグとして定義し、必要なオフセットを返す関数を配置します。データメンバー自体を定義するよりも。

ここで定義されているように、メンバーはパブリックである必要がありますが、保護されたプライベート プロパティをサポートするタグの「フレンド」宣言を簡単に追加できることに注意してください。それでは使ってみましょう。

struct foo
{
    int x;
    OFFSET_AWARE(foo, a);
    OFFSET_AWARE(foo, b);
    OFFSET_AWARE(foo, c);
    int y;
};

シンプルですね。

int main()
{
    foo f;

    cout << "foo f = " << &f << endl
        << "f.a: owner = " << f.a.owner() << endl
        << "f.b: owner = " << f.b.owner() << endl
        << "f.c: owner = " << f.c.owner() << endl;
    return 0;
}

これにより、すべての行に同じポインター値が出力されます。C++ 標準では、メンバーのサイズが 0 であることは許可されていませんが、ポインターの 4 または 8 (プラットフォームによって異なります) バイトと比較して、実際のコンテンツのサイズまたは空の場合は 1 バイトしかありません。

于 2011-02-08T20:50:22.450 に答える
2

1)適切と思われるgcc拡張機能があります:

enum{ d_y = __builtin_choose_expr(N,offsetof(X,y),0) };

しかし、マニュアルには
「組み込み関数は選択されなかった式を評価しない」と書かれていても、期待どおりに機能しませんでした。

2)メンバーポインタは面白そうだった。offsetofは、次のように定義できます。

template< class C, class T >
int f( T C::*q ) {
  return (int)&((*(C*)0).*q);
}

しかし、私はまだこれをconstexprに変える方法を見つけられませんでした。

3)今のところ、別のバージョンがあります:

#include <stdio.h>

#pragma pack(1)

template <class A, int x>
struct B {
  int z;
  void f( void ) {
    printf( "x=%i\n", x );
  }
};

#define STRUCT( A ) template< int N=0 > struct A {
#define CHILD( A, N, B, y ) }; template<> struct A<N> : A<N-1> \
  { B<A<N>,sizeof(A<N-1>)> y;
#define STREND };

STRUCT( A )
  int x0;
  int x1;
  CHILD( A,1, B, y );
  short x2;
  CHILD( A,2, B, z );
  char x3;
STREND

typedef A<2> A1;

int main( void ) {
  A1 a;
  a.y.f();
  a.z.f();
}
于 2011-02-08T22:45:05.963 に答える
1

今のところ、これがMS固有のソリューションの1つですが、それをより一般的にする方法をまだ考えています

#include <stdio.h>

#define offs(s,m)   (size_t)&(((s *)0)->m)
#define Child(A,B,y) \
  __if_exists(X::y) { enum{ d_##y=offs(X,y) }; } \
  __if_not_exists(X::y) { enum{ d_##y=0 }; } \
  B<A,d_##y> y;

template <class A, int x>
struct B {
  int z;
  void f( void ) {
    printf( "x=%i\n", x );
  }
};

template< class X >
struct A {
  int x0;
  int x1;
  Child(A,B,y);
  Child(A,B,z);
};

typedef A<int> A0;

typedef A<A0> A1;

int main( void ) {
  A1 a;
  a.y.f();
  a.z.f();
}
于 2011-02-08T20:00:07.907 に答える
0

呼び出しに含まれているオブジェクトへの参照が実際に必要であると仮定すると、所有者への参照を保存するだけです。余分な参照を保存するためにメモリが大幅に増加しているという特定のメモリ プロファイリングの証拠がない限り、明らかな方法で行ってください。

于 2011-02-08T19:16:33.407 に答える