プロパティを1バイトで収容する方法は?

エントリー


多くのプログラミング言語には、プロパティなどのツールがあります:C#、Python、Kotlin、Rubyなど。 このツールを使用すると、「フィールド」にアクセスするときにクラスメソッドを呼び出すことができます。 標準のC ++では、catの下でそれらをどのように実装できるかを知りたい場合は存在しません。


いくつかのポイント...



方法


誰もがget_xおよびset_xを使用した実装を知っていset_x


 class Complicated { private: int x; public: int get_x() { std::cout << "x getter called" << std::endl; return x; } int set_x(int v) { x = v; std::cout << "x setter called" << std::endl; return x; } }; 

それは最も明白な解決策であり、さらに、余分な変数はランタイムに保存されません( xフィールドを除き、 バッキングフィールドと呼ばれ、オプションで不要ではありません)、その主なマイナスは論理的にcx = (cx * cx) - 2 * (cx = cx / (4 + cx)) (具体的には、この例ではほとんど意味がありません)、 c.set_x((c.get_x() * c.get_x()) - 2 * c.set_x(c.get_x() / (4 + c.get_x()))) 。 そして、コード内の式が頭の中と同じに見えるようにします。


解説

好きなようにコードをカスタマイズできます。 inline追加するか、戻り値の型をvoid変更し、 バッキングフィールドまたはメソッドの1つを削除し、最後にconstvolatile割り当てます。これは推論に影響しません。 このような単純な算術式を求める多くの関数呼び出しは、少なくともat いように見えます。


オペレーター


C ++では、他のほとんどの言語と同様に、演算子(+、-、*、/、%、...)をオーバーロードできます。 しかし、これを行うには、ラッパーオブジェクトが必要です。


 class Complicated { public: class __property { private: int val; public: operator int() { // get std::cout << "x getter called" << std::endl; return val; } int operator=(int v) { // set val = v; std::cout << "x setter called" << std::endl; return val; } } x; }; 

cx = (cx * cx) - 2 * (cx = cx / (4 + cx))は人間に見えます。 他のComplicatedフィールドにアクセスする必要がある場合はどうなりますか?


 class Complicated { public: Axis a; class __property { public: operator int() { // get std::cout << "x getter called" << std::endl; return a.get_x(); // ???  'a'  __property  } int operator=(int v) { // set std::cout << "x setter called" << std::endl; return a.set_x(v); // ???  'a'  __property  } } x; }; 

演算子はComplicated::__property内でオーバーロードされているため、 これにはタイプComplicated::__property const*ます。 つまり、式cx = 2オブジェクトxはオブジェクトcについて何も知りません。 ただし、ゲッターとセッターの実装がComplicated何も必要としない場合、このオプションは非常に論理的です。


コメント
  • 軸は、たとえば、軸上の物理学を実装するオブジェクトです。
  • __propertyを匿名クラスにすることができます。
  • プロパティに補助フィールドがない場合、オブジェクトxは0ではなく1バイトを占有します。 ここでは、理由を明確に説明しています。 位置合わせにより、この数値は増加する場合があります。 したがって、メモリのすべてのバイトが非常に重要な場合は、最初のオプションを使用するだけで済みます。演算子をオーバーロードするに__property 、別の__propertyクラス__property必要です。

これを保存する


前の例では、 Complicatedへのアクセスが必要です。 また、 プロパティ自体の用語は、 get_xset_xComplicatedなメソッドとして定義されることを意味します。 そして、 Complicated内でメソッドを呼び出すには、そこから__propertyこれを知っている必要があります。


この方法も非常に明白ですが、最良ではありません。 ゲッターメソッド、セッターメソッド、外部クラスのthisなど、好きなものへのポインターを保存するだけです。 私はそのような実装を見てきましたが、なぜ人々がそれらを受け入れられると考えるのか分かりません。 プロパティのサイズは32(64)ビット、またはそれ以上に増加し、ポインターはプロパティのこれに非常に近いメモリに移動します(ほとんどの場合、それ自体を指します。その理由については後述します)。 これが私の最小限のオプションです。ポインタの代わりにリンクを使用するのが非常に適切です。


 class Complicated { private: Axis a; public: int get_x() { std::cout << "x getter called" << std::endl; return a.get_x(); } int set_x(int v) { std::cout << "x setter called" << std::endl; return a.set_x(v); } class __property { private: Complicated& self; public: __property(Complicated& s): self(s) {} inline operator int() { // get return self.get_x(); } inline int operator=(int v) { // set return self.set_x(v); } } x; Complicated(): x { *this } {} }; 

このアプローチは、最初の改良版と呼ぶことができます: メソッドを完全に含みます(UPD:それと以下のアプローチは、 Complicatedなメソッドとしてゲッターとセッターを使用したプロジェクトと完全に後方互換性があります)。 ご覧のとおり、機能はComplicatedで定義されており、 __propertyは多少抽象化されています。 それでも、実行時の価格とコンストラクターでプロパティの初期化を入力する必要があるため、この実装は好きではありません。


これを取得する


xフィールドは、 Complicatedオブジェクトの外部に存在してはならず、ラッパークラスも匿名である場合、各xほぼ確実にComplicatedオブジェクトに含まれます。 これは、 Complicatedの先頭を基準にしてxへのポインターからインデントを減算することにより、外部クラスからこれを取得することが比較的安全であることを意味します。


 class Complicated { private: Axis a; public: int get_x() { // get std::cout << "x getter called" << std::endl; return a.get_x(); } int set_x(int v) { // set std::cout << "x setter called" << std::endl; return a.set_x(v); } class __property { private: inline Complicated* get_this() { return reinterpret_cast<Complicated*>(reinterpret_cast<char*>(this) - offsetof(Complicated, x)); } public: inline operator int() { return get_this()->get_x(); } inline int operator=(int v) { return get_this()->set_x(v); } } x; }; 

ここで__propertyには抽象文字もあるため、必要に応じて一般化することができます。 唯一の欠点は、offsetofが複雑な(非POD 、したがってComplicated )タイプに適用できないことです。gccはこれについて警告します(明らかに、offsetofに必要なものを挿入するMSVCとは異なります)。


したがって、オフセットプロパティを適用できる単純な構造( PropertyHandler )に__propertyをラップし、次にstatic_castを使用してPropertyHandlerからこれComplicatedからキャストする必要があります(CompplicatedがPropertyHandlerから継承される場合)。これにより、すべてのインデントが正しく計算されます。


最終オプション


 template<class T> struct PropertyHandler { struct Property { private: inline const T* get_this() const { return static_cast<const T*>( reinterpret_cast<const PropertyHandler*>( reinterpret_cast<const char*>(this) - offsetof(PropertyHandler, x) ) ); } inline T* get_this() { return static_cast<T*>( reinterpret_cast<PropertyHandler*>( reinterpret_cast<char*>(this) - offsetof(PropertyHandler, x) ) ); } public: inline int operator=(int v) { return get_this()->set_x(v); } inline operator int() { return get_this()->get_x(); } } x; }; class Complicated: PropertyHandler<Complicated> { private: Axis a; public: int get_x() { std::cout << "x getter called" << std::endl; return a.get_x(); } int set_x(int v) { std::cout << "x setter called" << std::endl; return a.set_x(v); } }; 

ご覧のとおり、 static_castを実行できるようにテンプレートを作成する必要がありましたが、非常に便利に使用できるようにPropertyの定義を一般化することはできません。マクロでは非常に無愛想です(複雑な名前プロパティはカスタマイズできません)。


バッキングフィールドを使用しないこのような実装では、未使用のバイトが1つだけ(アライメントなしで)使用されます。 そして、ポインタを使用した実装のように機能します。 バッキングフィールドを使用すると、単一の「余分な」バイトは必要ありません。幸福のために他に必要なものは何ですか?


このアプローチの主な欠点は、ソースコードが歪んでいることですが、それがもたらす構文糖は努力する価値があると思います。


改善オプション
  • C ++の豊富さにより、独自の方法で他の演算子(割り当て、バイナリ演算など)を再定義できるため、ある種のキーワードまたは2つのアンパサンドがあるため、そのようなプロパティを自分で実装するのが理にかなっています(右辺値の演算子をオーバーロードすることを忘れないでください)大きなオブジェクトが使用されている場合)適切な場所に配置すると、プログラムの速度が大幅に向上します。 新しいデバッグの視野も開かれています...
  • C#よりも優れたアクセス修飾子をお楽しみいただけます! もちろん、よく考えて、適切なキーワードを適切な場所に配置してください。
  • たとえば、STLのコンテナのsize()size変わる(特にこの例では、最後ではなく最初の実装の1つを採用するのが理にかなっています-最も洗練された実装)、またはend 'ああ...

UPD:実際には、価格(1バイト単位)はプロパティの数に依存しません。すべてを統合することができるからです。


 template<class T> struct PropertyHandler { struct PropertyBase { protected: inline const T* get_this() const { return static_cast<const T*>( reinterpret_cast<const PropertyHandler*>( reinterpret_cast<const char*>(this) - offsetof(PropertyHandler, x) ) ); } inline T* get_this() { return static_cast<T*>( reinterpret_cast<PropertyHandler*>( reinterpret_cast<char*>(this) - offsetof(PropertyHandler, x) ) ); } }; union { class __x: PropertyBase { public: inline int operator=(int v) { return get_this()->set_x(v); } inline operator int() { return get_this()->get_x(); } } x; class __y: PropertyBase { public: inline double operator=(double v) { return get_this()->set_y(v); } inline operator double() { return get_this()->get_y(); } } y; }; }; class Complicated: public PropertyHandler<Complicated> { public: int get_x() { std::cout << "x getter called" << std::endl; return 1; } int set_x(int v) { std::cout << "x setter called" << std::endl; return 2 + v; } double get_y() { std::cout << "y getter called" << std::endl; return 3; } double set_y(double v) { std::cout << "y setter called" << std::endl; return 3 + v; } }; 


Source: https://habr.com/ru/post/J346936/


All Articles