web-dev-qa-db-ja.com

コンパイル時にクラスメンバーのオフセットを計算する方法は?

C++でクラス定義が与えられた

class A
{
  public:
    //methods definition
    ....

  private:
    int i;
    char *str;
    ....
}

C++テンプレートメタプログラミングを使用して、コンパイル時にクラスメンバーのオフセットを計算することは可能ですか?クラスはPODではなく、仮想メソッド、プリミティブ、およびオブジェクトデータメンバーを持つことができます。

15
Jun Yuan

Matthieu M.の回答に基づいていますが、短く、マクロはありません。

template<typename T, typename U> constexpr size_t offsetOf(U T::*member)
{
    return (char*)&((T*)nullptr->*member) - (char*)nullptr;
}

そしてそれはこのように呼ばれます:

struct X { int a, b, c, d; }

std::cout << "offset of c in X == " << offsetOf(&X::c);

編集:

ジェイソンライスは正しいです。これは、C++ 11で実際の定数式を生成しません。 http://en.cppreference.com/w/cpp/language/constant_expression -の制限を考えると、特にポインタの違いはなく、reinterpret_cast定数式にすることができます。

9
Glen Low

ええと... C++ 11では、実際には、通常のC++機能を使用して(つまり、特定のコンパイラ組み込み関数に委任せずに)そのようなオフセットを正しく計算できます。

liveworkspace で動作中:

_template <typename T, typename U>
constexpr int func(T const& t, U T::* a) {
     return (char const*)&t - (char const*)&(t.*a);
}
_

ただし、これはtがここでのconstexprインスタンスへの参照であることに依存しており、すべてのクラスに適用できるとは限りません。ただし、Tコンストラクターである限り、virtualconstexprメソッドを持ち、コンストラクターさえも持つことを禁じていません。

それでも、これはかなりの障害です。評価されていないコンテキストでは、実際にstd::declval<T>()を使用してオブジェクトを持つことをシミュレートできます。何も持っていない間。したがって、これはオブジェクトのコンストラクターに特定の要件を課しません。一方、そのようなコンテキストから抽出できる値はほとんどありません...そしてそれらは現在のコンパイラでも問題を引き起こします...まあ、それを偽造しましょう!

liveworkspace で動作中:

_template <typename T, typename U>
constexpr size_t offsetof_impl(T const* t, U T::* a) {
    return (char const*)t - (char const*)&(t->*a) >= 0 ?
           (char const*)t - (char const*)&(t->*a)      :
           (char const*)&(t->*a) - (char const*)t;
}

#define offsetof(Type_, Attr_)                          \
    offsetof_impl((Type_ const*)nullptr, &Type_::Attr_)
_

私が予測する唯一の問題は、ベースオブジェクトの実行時の配置のため、virtualの継承に関するものです。もしあれば、他の欠陥があれば喜んで提示されます。

5
Matthieu M.

いいえ、一般的ではありません。

OffsetofマクロはPOD(プレーンな古いデータ)構造体に存在し、C++ 0xを使用して標準のレイアウト構造体(または他の同様のわずかな拡張)にわずかに拡張できます。したがって、これらの制限されたケースについては、解決策があります。

C++は、コンパイラの作成者に多くの自由を提供します。一部のクラスがクラスのメンバーに対して変数オフセットを持つことを妨げる句はわかりませんが、コンパイラがなぜそうするのかはわかりません。 ;)

さて、コード標準に準拠し、オフセットを維持するための1つのアプローチは、データをPOD(またはC++ 0x拡張)サブ構造体に貼り付け、offsetofが機能し、そのサブ構造体で機能することです。クラス全体ではなく構造体。または、標準への準拠を放棄することもできます。クラス内の構造体のオフセットはわかりませんが、構造体内のメンバーのオフセットはわかります。

重要な質問は、「なぜこれが必要なのか、そして本当に正当な理由があるのか​​」ということです。

オリジナルのC++デザイナーの1人であるStanleyB.Lippmanによって書かれた1996年の本「InsidetheC++ Object Model」では、4.4章のPointer-to-Member関数に言及しています。

非静的データメンバーのアドレスを取得して返される値は、クラスレイアウト内のメンバーの位置のバイト値(プラス1)です。これは不完全な値と考えることができます。バインドする必要があります。メンバーの実際のインスタンスにアクセスする前のクラスオブジェクトのアドレス。

私は前世のどこかからその+1を漠然と思い出しますが、私はこれまでこの構文を見たり使用したりしたことがありません。

class t
{
public:
    int i;
    int j;
};
int (t::*pmf)() = &t::i;

少なくとも説明によれば、これは「ほぼ」オフセットを取得するためのクールな方法のようです。

しかし、少なくともGCCでは、もう機能していないようです。私は

Cannot initialize a variable of type 'int (t::*) with an rvalue of type "int t:: *'

ここで起こっていることの背後にある歴史を持っている人はいますか?このようなことはまだ可能ですか?

ウェブの問題-時代遅れの本は決して死ぬことはありません...

0
In Question