問題を解決したい場合-最初に答えを見つけてください
最後に何を取得する必要があるかを推定しましょう。
たとえば、「x」という名前のintフィールド。 この記録には非常に満足しています。
field(int,x);
さらに、コードではこのフィールドを参照します
foo.x = 10; int t = foo.x; foo.setField("x", 15); int p = foo.getField("x");
それでも、インストールを制御し、このフィールドから値を取得したい場合があります。そのため、ゲッターとセッターも作成する必要があります。
また、フィールドを初期化する可能性を忘れてはなりません。
どこから始めるか
実行時にフィールドについて何を知る必要がありますか? 少なくともその名前と意味。 そして、タイプを知ることは悪くありません。
種類
class Type { public: const std::string name; const size_t size; template <typename T> static Type fromNativeType() { return Type(typeid(T).name(), sizeof(T)); } Type(const char * name, size_t size) : size(size), name(name) { } Type(const Type & another) : size(another.size), name(another.name) { } };
これは、型を記述するクラスの完全な実装とはほど遠いものです。 実際、多くのものを追加することが可能であり、必要ですが、解決される問題にとってこれは最も重要なことではなく、名前とサイズで十分です。 おそらく、タイプの説明に専念する別の記事を書くでしょう。
多かれ少なかれ単純なように見えますが、静的メソッドだけが混乱します。 実際、構文では、テンプレート引数を三角括弧で渡すことにより、テンプレートコンストラクターをインスタンス化することはできません。
例
class Bar { public: template <int val> Bar() { int var = val; printf("%d\n", var); } };
Barクラス自体はテンプレートではありませんが、デフォルトのテンプレートコンストラクターがあります。 したがって、このコンストラクターを呼び出すには、インスタンス化する必要があります。 これは次のコードを頼みます:
Bar bar = Bar<10>();
ただし、このようなレコードは、テンプレートコンストラクターではなく、テンプレートクラスをインスタンス化することを意味します。
時々これを回避することができます、そしてそれから私は方法を示します。
したがって、Type :: fromNativeType <>()は、ある意味、コンストラクターでもあります。
フィールドストレージ
ランタイムから名前でフィールドにアクセスするため、何らかの方法でフィールドを保存する必要があります。 次のオプションを選択しました。他のすべてが継承されるベースクラスを作成します。 このクラスには、フィールド情報ストアとそれにアクセスするためのメソッドが含まれています。
class Basic { std::vector<FieldDeclaration> fields; public: template <typename FieldType> FieldType getField(const std::string & name, FieldType default) { for(int i = 0; i < fields.size(); ++i) { if (fields[i].name.compare(name)==0) { return static_cast< Field<FieldType>* >(fields[i].pointer)->getValue(); } } return default; } template <typename FieldType> void setField(const std::string & name, FieldType value) { for(int i = 0; i < fields.size(); ++i) { if (fields[i].name.compare(name)==0) { static_cast< Field<FieldType>* >(fields[i].pointer)->setValue(value); } } } };
おそらく、ストレージにstd :: mapを使用した方が良いでしょう。たとえば、std :: vectorが適しています。
FieldDeclarationは、型情報を含む単純な構造です。
struct FieldDeclaration { FieldDeclaration(const std::string & name, const Type & type, void * pointer = NULL) : name(name), type(type), pointer(pointer) { } const std::string name; const Type type; void * pointer; };
魔法の魔法
もちろん、このシステム全体は初めて作成されたものではなく、問題を解決するいくつかの方法が行き止まりになったという事実のために、その最も基本的な部分は何度も修正されました。
そのため、全体をまとめたコードフラグメントのみを埋め込みます。
使用されるいくつかの概念
#define __CONCAT__(a,b) a##b #define __STRINGIZE__(name) #name #define __CLASS_NAME__(name) __CONCAT__(__field_class__, name) #define __GETTER_NAME__(fieldname) __CONCAT__(getterof_, fieldname) #define __SETTER_NAME__(fieldname) __CONCAT__(setterof_, fieldname)
擬似キーワード
記事の冒頭で、フィールドの説明構文を使用することに同意しました。これは、フィールドのタイプと名前という2つの引数を取ります。 実際、私は2種類のフィールドを分離しました。
- smartfield-ゲッターとセッターをサポートし、実行時に名前で取得できます
- フィールド -ゲッターとセッターを使用しません
#define smartfield(type,name) \ type __stdcall __GETTER_NAME__(name)(); \ void __stdcall __SETTER_NAME__(name)(type value); \ __FIELD_CLASS_DECLARATION_SMART__(type,name) \ __CLASS_NAME__(name) name; #define field(type, name) \ __FIELD_CLASS_DECLARATION__(type,name) \ __CLASS_NAME__(name) name;
smartfieldマクロの最初の2行は、フィールドが配置されるクラスで、対応するフィールドのゲッターとセッターを直接宣言します。 次に、実装を作成する必要があります。 それらは、それぞれgetter_ <field name>およびsetter_ <field name>と呼ばれます。
__stdcall呼び出し規約修飾子を使用すると、ポインターでクラスメソッドを呼び出して、これを最初のパラメーターとして明示的に渡すことができます(既定で使用されるMicrosoft __thiscall規約は、これを渡すためにECXレジスタを使用します)。
__FIELD_CLASS_DECLARATION__および__FIELD_CLASS_DECLARATION_SMART__は、対応するフィールドのクラスの説明です(「内部キッチンクラス」に戻ります)。
__CLASS_NAME __(名前)名; これは実際には「家庭料理のクラス」のコピーです。
クラスField
「内部キッチンクラス」は、より一般的なフィールドクラスの子孫であることに注意してください。
#define NO_GETTER (TGetter)0 #define NO_SETTER (TSetter)0 template <typename FieldType> class Field { protected: typedef FieldType (*TGetter)(void *); typedef void (*TSetter)(void *, FieldType); TGetter getter; TSetter setter; void * that; public: const std::string name; const Type type; FieldType value; template< typename OwnerType > Field(OwnerType * _this, const char * nm) : name( nm ), type( Type::fromNativeType<FieldType>() ), getter(NO_GETTER), setter(NO_SETTER), that(_this) { _this->fields.push_back(FieldDeclaration(name, type, this)); } template< typename OwnerType > Field(OwnerType * _this, const char * nm, const FieldType & initvalue) : name( nm ), type( Type::fromNativeType<FieldType>() ), value(initvalue), getter(NO_GETTER), setter(NO_SETTER), that(_this) { _this->fields.push_back(FieldDeclaration(name, type, this)); } FieldType getValue() { if (getter) return getter(that); else return value; } void setValue(FieldType val) { if (setter) setter(that,val); else value = val; } Field<FieldType> & operator = (FieldType val) { setValue(val); return *this; } operator FieldType() { return getValue(); } };
したがって、テンプレートクラスFieldがあり、そのテンプレートはフィールドのタイプの指示を必要とします。
それ自体に格納するクラス:
- フィールド名
- フィールドタイプ情報
- 価値
- ゲッター
- セッター
- オーナークラスでこれに等しいポインター
TGetter型とTSetter型は、それらが記述する関数が最初のパラメーターとしてvoid *ポインターを取るように記述されていることに注意してください。 これは実際にはそのポインターです。 これは、ゲッターとセッターが__stdcall修飾子で明示的にマークされているため機能します。
今デザイナー。 それらはテンプレートであり、テンプレートはOwnerType所有者クラス、つまりフィールドが宣言されているクラスのタイプによってパラメーター化されます。 コンストラクター自体は、OwnerTypeクラスのthisポインターを取得して、その中に格納します。 ところで、私が言ったように、コンストラクターを明示的にパラメーター化することはできませんが、テンプレートには興味深い機能があります:テンプレートを自動的にパラメーター化する必要がある型を推測できる場合、これが起こります。 この場合、これは同じ状況です。 これをコンストラクタに渡すと、コンパイラ自体がOwnerType型を置き換えます。
引数nmは、フィールドのシンボル名を取ります。 上位のマクロから文字列演算子(上記の__STRINGIZE__を参照)によって作成されます。
デフォルトでは、呼び出す必要がないことを知るために、ゲッターとセッターをゼロ値で初期化します。 ゲッターとセッターが存在する場合、それらは相続クラスで個別に設定されます。
2番目のコンストラクタと最初のコンストラクタの違いは、デフォルトのフィールド値を使用することです。 かなり頻繁に使用されます。
次はデフォルトのゲッターとセッターです。 プログラマーが指定したゲッター/セッターをチェックし、設定されている場合は、最初のパラメーターとして明示的に転送して呼び出します。 それ以外の場合は、単に値を返すか、新しい値を割り当てます。
代入演算子とキャスト演算子は、フィールド値への構文的に便利なアクセスのためだけに必要です。
屋内キッチンクラス
#define __FIELD_CLASS_DECLARATION__(type, name) \ class __CLASS_NAME__(name) : public Field<type> \ { \ public: \ __FIELD_CLASS_CONSTRUCTOR_1__(type,name) \ __FIELD_CLASS_CONSTRUCTOR_2__(type,name) \ __CLASS_NAME__(name) & operator = (type val) \ { \ Field<type>::operator=(val); \ return *this; \ } \ }; #define __FIELD_CLASS_DECLARATION_SMART__(type, name) \ class __CLASS_NAME__(name) : public Field<type>\ { \ public: \ __FIELD_CLASS_CONSTRUCTOR_1_SMART__(type,name) \ __FIELD_CLASS_CONSTRUCTOR_2_SMART__(type,name) \ __CLASS_NAME__(name) & operator = (type val) \ { \ Field<type>::operator=(val); \ return *this; \ }\ };
これらのクラスは、所有者クラスに直接置き換えられます。 これらのクラスの名前を統一するには、__ CLASS_NAME__マクロを使用します(上記を参照)。 これらはすべて、すでに考慮されているFieldクラスの子孫です。
演算子によってそれ自体への参照の割り当てを返すことをお勧めします。これにより、カスケード割り当てを作成できます。
コンストラクターの2つの違い。
これらのクラスのコンストラクターについて
#define __FIELD_CLASS_CONSTRUCTOR_1_SMART__(type,name) \ template< class OwnerType > \ __CLASS_NAME__(name)(OwnerType * _this) \ : Field<type>(_this, __STRINGIZE__(name)) \ { \ auto get_ptr = &OwnerType::__GETTER_NAME__(name); \ auto set_ptr = &OwnerType::__SETTER_NAME__(name); \ this->getter = (TGetter)(void*)*(void**)(&get_ptr); \ this->setter = (TSetter)(void*)*(void**)(&set_ptr); \ } #define __FIELD_CLASS_CONSTRUCTOR_2_SMART__(type,name) \ template< class OwnerType > \ __CLASS_NAME__(name)(OwnerType * _this, type initvalue) \ : Field<type>(_this, __STRINGIZE__(name), initvalue) \ { \ auto get_ptr = &OwnerType::__GETTER_NAME__(name); \ auto set_ptr = &OwnerType::__SETTER_NAME__(name); \ this->getter = (TGetter)(void*)*(void**)(&get_ptr); \ this->setter = (TSetter)(void*)*(void**)(&set_ptr); \ } #define __FIELD_CLASS_CONSTRUCTOR_1__(type,name) \ template< class OwnerType > \ __CLASS_NAME__(name)(OwnerType * _this) \ : Field<type>(_this, __STRINGIZE__(name)) \ { \ } #define __FIELD_CLASS_CONSTRUCTOR_2__(type,name) \ template< class OwnerType > \ __CLASS_NAME__(name)(OwnerType * _this, type initvalue) \ : Field<type>(_this, __STRINGIZE__(name), initvalue) \ { \ }
図1と2は、フィールドの値を初期化するコンストラクター(2)となし(1)によって区別されます。 単語SMARTは、ゲッターとセッターの存在を示します。
すべてのコンストラクターも定型的なもので(タイプは保存され、フィールドコンストラクターに渡される必要があります)、同じ方法でOwnerType自動置換を使用します。 対応するFieldコンストラクタが呼び出され、これと初期化値(存在する場合)に加えて、__ STRINGIZE__マクロによって取得された文字列const char []とともにフィールド名も送信されます。
次に、SMARTコンストラクターで、ゲッターとセッターへのポインターの取得と保存が実行されます。 非常に奇妙に動作します。 実際のところ、C ++は、クラスメソッドへのポインターの型の変換を厳密に指すということです。 これは、継承および仮想メソッドの可能性を考えると、メソッドへのポインターが関数へのポインターと同じ方法で表現できるとは限らないという事実によるものです。 ただし、ゲッターとセッターへのポインターは、たとえばvoid *型で表現できることがわかっています。
C ++コンパイラが与える方法へのポインタを格納する一時変数を作成します。 タイプautoを作成しました。実際、明示的に作成することもできますが、より便利であり、C ++ 0xのおかげです。
次に、これらの一時変数へのポインターを取得します。 これらのポインターはvoid **にキャストされます。 次に、逆参照してvoid *を取得します。 結局、私たちはすでにTGetterまたはTSetterの種類を用意して保存しています。
最後のタッチ
フィールドは通常の操作にthisポインターを必要とするため、すべてのフィールドを初期化する必要があります。 したがって、これを便利に行うことができる小さなマクロを作成するとよいでしょう。
#define initfieldval(name, value) name(this, value) #define initfield(name) name(this)
1つ目は値の初期化用で、2つ目は単純な初期化用です。
以上です!
使用する
#include "basic.h" class Foo : public Basic { public: smartfield(int, i); field(float, f); Foo(); }; Foo::Foo() : initfield(i), initfieldval(f, 3.14) { } int Foo::getterof_i() { printf("Getting field i of class Foo\n"); return i.value; } void Foo::setterof_i(int value) { printf("Setting field i of class Foo\n"); i.value = value; } int main() { Foo foo; int j = foo.i; foo.setField("i", 10); int k = foo.getField("i", -1); float z = foo.f; return 0; }
おわりに
そのため、実行時に名前でアクセスする機能と、かなり単純な構文でセッターとゲッターを使用する機能を持つクラスフィールドなどのツールを取得しました。 これがタスクの最善の解決策であると主張するのではなく、逆に、これをどのように改善できるかについてのアイデアを持っています。
マイナスのうち、静的フィールドを作成することは不可能であることに注意してください(現時点では)。2つの異なる単語を使用して、デフォルト値のあるフィールドとないフィールドを初期化する必要があります。
ソースコード
PS
ここに書かれているものはすべて、C ++への愛だけから生まれました。
もちろん、私は自分の作品にそのようなことを書くことは決してありませんし、コードを読むのは非常に難しいので、他の人には勧めません。
PS2
プリプロセッサには、少なくとも引数の数だけマクロをオーバーロードする機能がないことに非常に怒っており、これを妨げるものは何もないと思います。
引数の数でマクロをオーバーロードできる場合、フィールド初期化マクロはさらに美しく見えます。