エントリー
多くのプログラミング言語には、プロパティなどのツールがあります:C#、Python、Kotlin、Rubyなど。 このツールを使用すると、「フィールド」にアクセスするときにクラスメソッドを呼び出すことができます。 標準のC ++では、catの下でそれらをどのように実装できるかを知りたい場合は存在しません。
いくつかのポイント...
- 私はBjarne Stroustrupではないので、何かの内部構造について間違っている可能性があります。コメントを修正できてうれしいです。
- この記事では、プロパティを実装するためのアイデアのみを示しています。 さまざまなオプションがさまざまな状況に適しています。記事の最後には、既製のライブラリまたはヘッダーファイルはありません。
方法
誰もが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つを削除し、最後にconst
とvolatile
割り当てます。これは推論に影響しません。 このような単純な算術式を求める多くの関数呼び出しは、少なくともat いように見えます。
オペレーター
C ++では、他のほとんどの言語と同様に、演算子(+、-、*、/、%、...)をオーバーロードできます。 しかし、これを行うには、ラッパーオブジェクトが必要です。
class Complicated { public: class __property { private: int val; public: operator int() {
cx = (cx * cx) - 2 * (cx = cx / (4 + cx))
は人間に見えます。 他のComplicated
フィールドにアクセスする必要がある場合はどうなりますか?
class Complicated { public: Axis a; class __property { public: operator int() {
演算子はComplicated::__property
内でオーバーロードされているため、 これにはタイプComplicated::__property const*
ます。 つまり、式cx = 2
オブジェクトx
はオブジェクトc
について何も知りません。 ただし、ゲッターとセッターの実装がComplicated
何も必要としない場合、このオプションは非常に論理的です。
コメント- 軸は、たとえば、軸上の物理学を実装するオブジェクトです。
__property
を匿名クラスにすることができます。- プロパティに補助フィールドがない場合、オブジェクトxは0ではなく1バイトを占有します。 ここでは、理由を明確に説明しています。 位置合わせにより、この数値は増加する場合があります。 したがって、メモリのすべてのバイトが非常に重要な場合は、最初のオプションを使用するだけで済みます。演算子をオーバーロードするに
__property
、別の__property
クラス__property
必要です。
これを保存する
前の例では、 Complicated
へのアクセスが必要です。 また、 プロパティ自体の用語は、 get_x
とset_x
がComplicated
なメソッドとして定義されることを意味します。 そして、 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() {
このアプローチは、最初の改良版と呼ぶことができます: メソッドを完全に含みます(UPD:それと以下のアプローチは、 Complicated
なメソッドとしてゲッターとセッターを使用したプロジェクトと完全に後方互換性があります)。 ご覧のとおり、機能はComplicated
で定義されており、 __property
は多少抽象化されています。 それでも、実行時の価格とコンストラクターでプロパティの初期化を入力する必要があるため、この実装は好きではありません。
これを取得する
x
フィールドは、 Complicated
オブジェクトの外部に存在してはならず、ラッパークラスも匿名である場合、各x
ほぼ確実にComplicated
オブジェクトに含まれます。 これは、 Complicated
の先頭を基準にしてx
へのポインターからインデントを減算することにより、外部クラスからこれを取得することが比較的安全であることを意味します。
class Complicated { private: Axis a; public: int get_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; } };