2

C++ には言語の一部としてフィールド アクセサーがないことについて の記事を読んでいました。

投稿の最後で、著者は、クラスのフィールド アクセサーをエミュレートするマクロ ベースのソリューションを提供します。

// a little trick to fool compiler we are not accessing NULL pointer here
#define property_offset(type, name) \
  (((char*)&((type*)(0xffff))->name) - (char*)(0xffff))

#define property_parent(type, name) \
  ((type*)((char*)(this) - property_offset(type, name)))

// macro defining property
#define property(type, name, parent)                                         \
  struct name##_property {                                                   \
    operator type() { return property_parent(parent, name)->get_##name(); }  \
    void operator=(type v) { property_parent(parent, name)->set_##name(v); } \
                                                                             \
   private:                                                                  \
    char zero[0];                                                            \
  } name

// our main class
class Node {

  /* visitCount will act as a field accessor */
  property(int, visitCount, Node);
};

これをプリプロセッサで実行すると、次のようになります。

class Node {

  struct visitCount_property {
    operator int() { return ((Node*)((char*)(this) - (((char*)&((Node*)(0xffff))->visitCount) - (char*)(0xffff))))->get_visitCount(); }
    void operator=(int v) { ((Node*)((char*)(this) - (((char*)&((Node*)(0xffff))->visitCount) - (char*)(0xffff))))->set_visitCount(v); }    
    private: char zero[0];
    } visitCount;
};  

アイデアは、私自身の実装も追加したであろうということです:

int get_visitCount();
void set_visitCount(int v); 

そして、あたかもvisitCount直接アクセスされているように見えます。
ただし、関数は実際には舞台裏で呼び出されます。

Node n;
n.visitCount = 1;     //actually calls set method
cout << n.VisitCount; //actually calls get method  

囲んでいるクラスにアクセスするこのトリックについて詳しく知りたいです。

((Node*)((char*)(this) - (((char*)&((Node*)(0xffff))

の関連性は0xffff何ですか?
10 進数では: 65535.

これはどのようにコンパイラをだまして、visitCount クラスを囲むクラスにアクセスさせるのでしょうか?

また、これは MSVC では機能しないこともわかっているので、このハックが行っていることを達成する標準的な方法があるかどうか疑問に思っていました。

4

1 に答える 1

3

の関連性はありません0xffff。ちょっとした数字です。ゼロになる可能性があります (実際、ゼロであればより簡単です)。これをバラバラにして、次のように書き直しましょ0xffffaddr

(((char*)&((type*)(addr))->name) - (char*)(addr))

(type*)(addr)type*で始まるものをいくつか示しますaddr。それはreinterpret_castです。それでは、それを呼び出しましょうobj:

 (((char*)&obj->name) - (char*)(addr))

addr2 番目を次のように置き換えることもできobjます。

 (((char*)&obj->name) - (char*)(obj))

&obj->nameその特定のメンバーへのポインターを提供するだけなので、それを呼び出しましょうmem_ptr

((char*)mem_ptr) - (char*)(obj)

これで明らかです。メンバーのアドレスを ( として) 取得しchar*、親オブジェクトのアドレスを ( として) 減算していますchar*。以前0xffffは、両方の場所で最初の住所が同じだっただけです。

C++ 標準では、このためのマクロも直接定義されていることに注意してください。と呼ばれていoffsetofます。「型が標準レイアウト クラスでない場合 (第 9 節)、結果は未定義です」という注意事項があります。

于 2015-05-26T03:28:37.417 に答える