__property

提供: RAD Studio
移動先: 案内検索

C++ 拡張キーワード への移動


このセクションでは、C++ において UI 作成に使用される PME(プロパティ-メソッド-イベント)モデルの主要要素となる、C++ Builder の __property キーワードに対するサポートについてみていきます。

プロパティは、コードでアクセスする場合、構文的にはフィールドのように見えますが、フィールドまたはメソッドによって読み取りまたは書き込みを行うことができます。これにより、読み取り時の遅延評価や書き込み時のデータの無効化など、読み取りと書き込みに副作用が生じる可能性があります。

以下は、mX というフィールドから読み取り、setX() メソッドを介して書き込むプロパティの簡単な例です:

int mX;
void setX(int value);
__property int X = {read = mX, write = setX};

コードで使用すると、フィールドのような構文になります:

int foo = myObject->X;
myObject->X = 5;

プロパティを読み取る最初の行は、mX フィールドから読み取ります。この例では 書き込み アクセス指定子がメソッドを使用しているため、プロパティに値を代入する 2 行目でメソッドに値を渡す setX メソッドを呼び出します。アクションを実行するのはメソッド次第です: つまり、プロパティを設定 (書き込み) するときに mX フィールドを本当に更新したいのなら、メソッドは mX の値を設定しなければなりません。

コンポーネントの開発者は、通常、プロパティを使用します。プロパティは、次の2 つの点において、UI コントロールを処理する便利な方法を提供します:

  • 動作: 値の割り当て。値が割り当てられたときに UI コントロールがアクションを実行する必要がある場合。
  • ストリーミング: UI コントロールとその値のセットを保存および復元。

どの C++ コードでもプロパティを使用できますが、ほとんどの場合、VCL または FMX ユーザー インターフェイスを使用するコードを記述することによってのみプロパティを操作します。このようなコードは、プロパティの仕組みを理解しておくとよいのですが、フィールドのようなアクセスとして扱い、プロパティのアドレスや参照を取得できないことを考慮すると、有効に使用できます。

プロパティは UI 設計の鍵です。__published 指定子を使用するプロパティは、ユーザー インターフェイスを構築する際にはオブジェクト インスペクタに表示され、設計時にはオブジェクト インスペクタに、実行時にはコードで、設定することができます。

UI 設計では、クロージャタイプ(オブジェクト指向のメソッド ポインタ)のプロパティは、イベントの格納に使用されます。

C++Builder のプロパティには、インデックス アクセス、ストリーミング時のデフォルト値など、他にも多くの機能があり、非常に強力な言語機能となっています。

__property 構文

__property の宣言は、名前 を指定し、少なくとも 1 つのアクセス指定子も含みます。プロパティ宣言の構文は次のとおりです:

__property type propertyName = { attributes };

またはインデックス付きプロパティの場合、

__property type propertyName [index1Type index1 ][indexNType indexN ] = { attributes };

ここでは次のとおり:

  • type は、組み込みまたは事前に宣言済みのデータ型。
  • <プロパティ名> は任意の有効な識別子です。
  • indexNType は、組み込みまたは事前に宣言済みのデータ型。
  • indexN は、インデックス パラメータの名前
メモ: 角かっこ内の indexN パラメータは任意です。存在する場合は、配列プロパティを宣言します。
  • attributes は、readwritestoredindexdefault(または nondefault)、implements 指定子のカンマ区切りのシーケンスです。
メモ: すべてのプロパティ宣言には、少なくとも 1 つの読み取りまたは書き込み指定子が必要です。

クラスに次のフィールドとメソッドがあると仮定して、以下の例を分析してみましょう:

private:
      int readX;
      void writeX(int Value);
      double GetZ();
      float cellValue(int row, int col);
      void setCellValue(int row, int col, float Value);
      int GetCoordinate(int Index);
      void SetCoordinate(int Index, int Value);

次のプロパティ宣言が有効です:

	// Standard, common property declaration, reading from a field and writing via a method:
	__property int X = {read = readX, write = writeX};
	// A read-only property (note the absence of the write specifier.) 
	__property double Z = {read = GetZ};
	// An indexed property - note two indices in use:
	__property float Cells[int row][int col] = {read = cellValue,
		write = setCellValue};
	// Redeclaring a property declared in an ancestor class (used for redeclaring in a wider visibility scope, such as bringing an ancestor protected property to [[public]] or [[__published]] scope):
	__property Foo;
	// Wrapping an indexed method into a simple property:
	__property int Left = { index = 0, read = GetCoordinate,
		write = SetCoordinate };
	// Another indexed method wrapped to a simple property, this time with specifiers used for streaming:
	__property int Top = { read = GetCoordinate,
		write = SetCoordinate, index = 1, stored = true, default = 5 };

properties キーワードにはいくつかの制限があります:

  • プロパティのアドレスは取得できません。
  • プロパティを参照で渡せません。

これは、フィールドまたはメソッドのいずれかによってサポートされる構文機能であるためです。

___property のアクセス

__property 宣言には、read、write、またはその両方の指定子がなければなりません。これらはアクセス指定子と呼ばれ、次の形式をとります:

read = _ // member
write = _ // member
メモ: member はフィールドまたはメソッドになる可能性があり、通常は private または protected ですが、どの可視性でも配置することができます。

メソッドで read 指定子を使用すると、メソッドはパラメータなしのプロパティと同じ型を返します。

メソッドで write 指定子を使用すると、メソッドは void を返し、同じ型の単一のパラメータと、任意の配列インデックスのパラメータを持ちます。

配列プロパティ

配列プロパティはインデックス付きプロパティです。これらは、リスト内の項目、コントロールの子コントロール、ビットマップのピクセルなどを表すことができます。

配列プロパティの宣言には、インデックスの名前と型を指定するパラメータ リストが含まれます。

__property type PropertyName[IndexType IndexName]

複数のインデックスを持つプロパティでは、複数の配列宣言が互いに続く場合があります:

__property type PropertyName[IndexTypeA IndexNameA][IndexTypeB IndexNameB]

配列とは異なり、配列プロパティでは任意の型のインデックスを使用できます。配列プロパティの場合、アクセス指定子はフィールドではなくメソッドをリストにする必要があります:

  • read 指定子と write 指定子のメソッドは、プロパティのパラメータ リストの数と型を同じ順序で取る関数でなければなりません。
  • read 指定子を使用すると、メソッドの結果の型はプロパティの型と同じになります。
  • write 指定子を使用する場合は、プロパティと同じ型の値または const パラメータを追加する必要があります。

インデックス指定子

インデックス指定子を使用すると、複数のプロパティで同じアクセス方法を共有しながら、異なる値を表すことができます。インデックス指定子は、指令インデックスとそれに続く -2147483647 ~ 2147483647 の整数定数で構成されます。例:

class TRectangle {

private:
   float FCoordinates[4];

protected:
	float GetCoordinate(int index){
		return FCoordinates[index];
	}

	void SetCoordinate(int index, float value){
		 FCoordinates[index]=value;
	}

public:
	__property int Left = {read = GetCoordinate, write =  SetCoordinate, index = 0};
	__property int Top = {read = GetCoordinate, write = SetCoordinate, index = 1};
	__property int Right = {read = GetCoordinate, write = SetCoordinate, index = 2};
	__property int Bottom = {read = GetCoordinate, write = SetCoordinate, index = 3};
	__property int Coordinates[int index] = {read = GetCoordinate, write = SetCoordinate};
};

プロパティにインデックス指定子がある場合、その read および write 指定子は、フィールドではなくメソッドをリストにする必要があります。 インデックス指定子を持つプロパティのアクセス メソッドは、Integer 型の追加の値パラメーターを取る必要があります。

  • read 関数の場合、これは最後のパラメータでなければなりません
  • write プロシージャの場合、最後から 2 番目のパラメータ(プロパティ値を指定するパラメーターの前)が必要です

プログラムがプログラムにアクセスすると、プロパティの整数定数は、自動的にアクセス メソッドに渡されます。

警告: Delphi のデフォルトのインデックス プロパティは、C++ の添字演算子と競合します。したがって、Delphi のすべてのデフォルト インデックス プロパティは、基になるプロパティに転送されるメンバー演算子 [] (int インデックス)として公開されます。

ストレージ指定子

任意の指令 storeddefaultnodefault (ストレージ指定子と呼ばれる)は、プログラムの動作には影響しませんが、公開されたプロパティの値をフォーム ファイルに保存するかどうかを制御します。ストリーミング VCL および FMX コントロールは、RTL によって管理されます。VCL または FMX コンポーネントを開発するときに、これらのパラメータを使用します。

stored

この指令は、フォーム ストリーミング システムで使用されます。デフォルトでは、デフォルトでは、すべてのプロパティが .dfm ファイルにストリーミングされます。これにより、条件付きまたは常にプロパティをストリーミングから除外できます。 stored を true に設定してプロパティを強制的にストリーミングすることはできません。false に設定すると、ストリーミングされなくなることのみ可能です。

stored 指令の後には、truefalse、そしてブーリアン フィールドの名前、または Boolean 値を返すパラメータなしメソッドの名前が続く必要があります。

stored = [Boolean]

プロパティに stored 指令がない場合、デフォルトで True として処理されます。

default と nodefault

default 指令では、プロパティのデフォルト値を指定することができます。これは、オブジェクト インスペクタによって使用され、それがデフォルトではないときに、プロパティを異なるよう描画できるようにします。プロパティはまた、それらの値がデフォルト値と一致する際にはストリームされません。

デフォルト値を指定してもプロパティは初期化されないことに注意してください。クラスのコンストラクタでフィールドを初期化する必要があります(フィールドによってサポートされるプロパティの場合)。そして、プロパティをサポートするフィールドを初期化した値を変更する場合には、default キーワードで使用される値も更新する必要があります。プロパティで使用されるフィールドに対してこの指令の値を検証するコンパイラ警告はありません。

default 指令の後には、プロパティと同じ型の定数が続く必要があります。

default = _ (value of the same type)
nodefault

nodefault 指令は、新しい値を指定せずに継承された default の値をオーバーライドするために使用されます。プロパティを指定するときにデフォルト値を指定しない場合、デフォルトはありません。これは、先祖がデフォルトを指定していて、それをリセットしたい場合のプロパティの再宣言に使用されます。

default および nodefault 指令は序数型とセット型のみをサポートし、セットの上限および下限は 0 ~ 31 の序数値を持つようにします。プロパティが default または nodefault として宣言されていなければ、nodefault として扱われます。浮動小数点数型、ポインタ、文字列の場合、それぞれ 0、nullptr、 (空文字列) の暗黙のデフォルト値があります。

メモ: 序数値 -2147483648 をデフォルト値として使用することはできません。この値は、内部的に nodefault を表すために使用されます。

stored、default/nodefault、およびストリーミング プロパティ

これらの指定子は、クラスをストリーミングする際に使用されます。プロパティの現在値がその default 値と異なる場合(または、default を指定しなかったか nodefault を指定することにより、default 値がない場合)、または stored 指定子が true の場合、プロパティの値はストリーミング時に保存されます。そうでなければ、保存されません(書き込まれません)。

警告: ストレージ指定子は、配列プロパティではサポートされていません。デフォルト指令は、配列プロパティの宣言で使用される場合、別の意味を持ちます。上記の配列プロパティを参照してください。

Implements 属性

C++Builder では、__property キーワードに対する imprements 属性が導入されています。implements 属性により、複数の継承を行うことなく、インターフェイスを効率的に実装できます。

C++ __property キーワードの implements 属性では、それを、クラスの属性かフィールドとして指定することで、インターフェイスを実装することができます。この実装は Delphi が指令を実装している方法と同様で、これにより、クラスが、プロパティを委譲することでインターフェイスを実装できるようにします。__property 文では、implements 属性は、nodefault 属性の位置と同様、最後に置かれます。

implements 属性の詳細については、ここをクリックしてください。

プロパティのオーバーライドと再宣言

型を指定しないプロパティ宣言は、プロパティ オーバーライドと呼ばれます。プロパティ オーバーライドにより、プロパティの継承された可視性や指定子を変更できます。

最もシンプルなオーバーライドは、予約語 __property とそれに続く継承されたプロパティ識別子のみで構成されます。この形式は、プロパティの可視性を変更する際に使用されます。</br> たとえば、上位クラスがプロパティを protected として宣言している場合、派生クラスは、クラスの public または published セクションでそれを再宣言できます。プロパティ オーバーライドには、readwritestoreddefaultnodefault 指令を含めることができます。このような指令はいずれも、対応する継承指令をオーバーライドします。

オーバーライドは、継承されたアクセス指定子を置き換えたり、不足している指定子を追加したり、プロパティの可視性を増やすことはできますが、アクセス指定子を削除したり、プロパティの可視性を減らすことはできません。オーバーライドは、implements 指令を含むことができ、実装済みインターフェイスのリストに、継承したインターフェイスを削除することなく追加することができます。

次の例では、Left プロパティを __published 可視性で再宣言しています。上位クラスでは、public または protected の可視性であったのでしょう:

__published
  __property Left;
The following example redeclares a Left property, adding the stored specifier:
__property Left = { stored = true };

型識別子を含むプロパティの再宣言は、軽症されたプロパティをオーバーライドするのではなく、非表示にします。これは、新しいプロパティが、継承されたものと同じ名前で作成されることを意味します。型を指定するプロパティ宣言は、完全である必要があるため、少なくとも 1 つはアクセス指定子を含む必要があります。

__property 例

次の例は、いくつかシンプルなプロパティ宣言を示しています:

class PropertyExample {
private:
	int Fx, Fy;

	float Fcells[100][100];
protected:
	int readX() {
		return (Fx);
	}
	void writeX(int newFx) {
		Fx = newFx;
	}
	double computeZ() {

		// Do some computation and return a floating-point value...
		return (0.0);
	}
	float cellValue(int row, int col) {
		return (Fcells[row][col]);
	}
public:
	__property int X = {read = readX, write = writeX};
	__property int Y = {read = Fy};
	__property double Z = {read = computeZ};
	__property float Cells[int row][int col] = {read = cellValue};

};

この例では、いくつかプロパティ宣言を示しています:

  • プロパティ X の読み取りと書き込みは、それぞれメンバ関数 readX と writeX を通じて行えます。
  • プロパティ Y は、メンバー変数 Fy に直接対応し、読み取り専用です。
  • プロパティ Z は、計算された読み取り専用値で、クラスのデータ メンバとしては格納されません。
  • Cells プロパティは、2 つのインデックスを持つ配列プロパティを示しています。

次の例では、コードでこれらのプロパティにアクセスする方法を示しています:

// Previous code goes here
int main(int argc, char * argv[]) {
 PropertyExample myPropertyExample ;
 myPropertyExample.X = 42; // Evaluates to: myPropertyExample.WriteX(42) ;
 int  myVal1 = myPropertyExample.Y; // Evaluates to: myVal1 = myPropertyExample.Fy ;
 double  myVal2 = myPropertyExample.Z; // Evaluates to: myVal2 = myPropertyExample.ComputeZ() ;
 float  cellVal = myPropertyExample.Cells[3][7]; // Evaluates to : cellVal = myPropertyExample.cellValue(3,7);
}

コンパイラのサポート

__property キーワードのサポートは、clang(推奨)またはクラッシックと、使用する C++ コンパイラによって変わります。特に、clang コンパイラでの __property 実装では、クラシック bcc32 が処理しない機能をいくつか処理しています。

主な違いは、次の例で説明します:

#include <string>
typedef std::string PROPTYPE;

class TTest {
  PROPTYPE getProp();
  void setProp(PROPTYPE);
public:
  __property PROPTYPE Val = { read=getProp, write=setProp };  
};

void test() {
  TTest t;
  t.Val += "hello";  // <<<<< **
}

PROPTYPE TTest::getProp()         { printf("%s\n", __FUNCTION__); return PROPTYPE(); }
void     TTest::setProp(PROPTYPE) { printf("%s\n", __FUNCTION__); }

int main() {
  test();
}

コードでは、クラシック bcc32 コンパイラは取得関数を呼び出し、返された一時データを操作している一方、clang コンパイラは同じことを行いますが、その結果で設定関数も呼び出しています。つまり、clang コンパイラだけが、開発者が += 行の動作として直感的に期待するようにふるまうコードを生成します。

関連項目