__property

Aus RAD Studio
Wechseln zu: Navigation, Suche

Nach oben zu Schlüsselwort-Erweiterungen in C++


In diesem Abschnitt wird die Unterstützung von C++Builder für das Schlüsselwort __property, ein Schlüsselelement im PME-Modell (Property-Method-Event, Eigenschaft-Methode-Ereignis) erläutert, das zur Erstellung der UI in C++ verwendet wird.

Eine Eigenschaft wird in Bezug auf die Syntax wie ein Feld dargestellt, wenn im Code darauf zugegriffen wird, aber das Lesen oder Schreiben kann durch ein Feld oder eine Methode unterstützt werden. Dadurch können beim Lesen und Schreiben Nebeneffekte auftreten, z. B. eine träge Auswertung beim Lesen oder das Ungültigmachen von Daten beim Schreiben.

Hier ist ein einfaches Beispiel einer Eigenschaft, die aus einem Feld mit dem Namen mX liest und mithilfe der Methode setX() schreibt:

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

Wenn die Eigenschaft im Code verwendet wird, hat sie eine feldähnliche Syntax:

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

In der ersten Zeile (Lesen der Eigenschaft) wird aus dem Feld mX gelesen. Weil in diesem Beispiel der Zugriffsbezeichner write eine Methode verwendet, wird in der zweiten Zeile (Zuweisen eines Werts zu der Eigenschaft) die Methode setX aufgerufen und der Wert an die Methode übergeben. Die Methode muss dann eine Aktion ausführen: das heißt, wenn Sie möchten, dass das Feld mX wirklich aktualisiert wird, wenn die Eigenschaft festgelegt (in sie geschrieben) wird, muss die Methode den Wert von mX festlegen.

In der Regel verwenden Komponentenentwickler Eigenschaften. Eigenschaften bieten eine bequeme Möglichkeit, UI-Steuerelemente zu handhaben, und zwar in Bezug auf:

  • Verhalten: Zuweisen von Werten, wobei ein UI-Steuerelement möglicherweise eine Aktion ausführen muss, wenn ein Wert zugewiesen wird.
  • Streaming: Speichern und Wiederherstellen einer Gruppe von UI-Steuerelementen und ihrer Werte.

Sie können Eigenschaften in jedem C++-Code verwenden, aber höchstwahrscheinlich interagieren Sie mit ihnen nur durch Code, der VCL- oder FMX-Benutzeroberflächen verwendet. Bei solchen Codes ist es zwar gut zu verstehen, wie eine Eigenschaft funktioniert, aber Sie können sie effektiv nutzen, indem Sie sie als feldähnlichen Zugriff behandeln und berücksichtigen, dass Sie die Adresse einer Eigenschaft oder eine Referenz auf eine Eigenschaft nicht abrufen können.

Eigenschaften sind ein Schlüsselelement des UI-Designs und Eigenschaften mit dem Bezeichner __published werden beim Erstellen einer Benutzeroberfläche im Objektinspektor angezeigt und können beim Entwurf im Objektinspektor oder zur Laufzeit im Code festgelegt werden.

Beim UI-Design werden Eigenschaften vom Typ __closure – ein objektorientierter Methodenzeiger – für die Aufnahme von Ereignissen verwendet.

Die Eigenschaften von C++Builder verfügen über viele weitere Features, wie Zugriff über Index, Standardwerte beim Streaming und vieles mehr. Dadurch sind Eigenschaften ein sehr leistungsstarkes Sprach-Feature.

Syntax von __property

In der Deklaration von __property wird ein Name und ein Typ und mindestens ein Zugriffsbezeichner angegeben. Die Syntax einer Eigenschaftsdeklaration lautet:

__property type propertyName = { attributes };

oder für eine indexierte Eigenschaft:

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

Bedeutung der Parameter:

  • type ist ein integrierter oder bereits deklarierter Datentyp.
  • propertyName ist ein beliebiger gültiger Bezeichner.
  • indexNType ist ein integrierter oder bereits deklarierter Datentyp.
  • indexN ist der Name eines Indexparameters.
Hinweis: Die indexN-Parameter in eckigen Klammern sind optional. Wenn sie vorhanden sind, deklarieren Sie eine Array-Eigenschaft.
  • attributes ist eine durch Komma getrennte Folge von read-, write-, stored-, index-, default- (oder nondefault-) oder implements-Bezeichnern.
Hinweis: Jede Eigenschaftsdeklaration muss zumindest einen read- oder write-Bezeichner enthalten.

Im Folgenden werden die Beispiele unten analysiert, wobei von den folgenden Feldern und Methoden in einer Klasse ausgegangen wird:

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);

Die folgenden Eigenschaftsdeklarationen sind gültig:

	// 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 };

Für das Schlüsselwort property gelten einige Beschränkungen:

  • Sie können die Adresse der Eigenschaft nicht abrufen.
  • Sie können eine Eigenschaft nicht per Referenz übergeben.

Da es sich um ein Syntaxmerkmal handelt, das entweder durch ein Feld oder eine Methode unterstützt werden kann.

Zugriff auf ___property

Eine __property-Deklaration muss über einen read- und/oder einen write-Bezeichner verfügen. Diese werden Zugriffsbezeichner genannt und haben die folgende Form:

read = _ // member
write = _ // member
Hinweis: member kann ein Feld oder eine Methode sein, in der Regel als private oder protected deklariert, aber Sie können jede Sichtbarkeit dafür angeben.

Wenn Sie den read-Bezeichner mit einer Methode verwenden, gibt die Methode denselben Typ wie die Eigenschaft ohne Parameter zurück.

Wenn Sie den write-Bezeichner mit einer Methode verwenden, gibt die Methode "void" zurück und sie hat einen einzelnen Parameter mit demselben Typ plus Parameter für jeden Array-Index.

Array-Eigenschaften

Array-Eigenschaften sind indizierte Eigenschaften. Sie werden beispielsweise für die Einträge einer Liste, die untergeordneten Objekte eines Steuerelements oder die Pixel einer Bitmap verwendet.

Die Deklaration einer Array-Eigenschaft enthält eine Parameterliste mit den Namen und Typen der Indizes.

__property type PropertyName[IndexType IndexName]

Bei Eigenschaften mit mehreren Indizes können mehrere Array-Deklarationen nacheinander angegeben werden:

__property type PropertyName[IndexTypeA IndexNameA][IndexTypeB IndexNameB]

Im Gegensatz zu Arrays können für Array-Eigenschaften Indizes jeden Typs verwendet werden. Bei Array-Eigenschaften müssen die Zugriffsbezeichner keine Felder, sondern Methoden angeben:

  • Die Methode in einem read- und write-Bezeichner muss eine Funktion sein, die die Anzahl und den Typ der Parameter aus der Parameterliste einer Eigenschaft in derselben Reihenfolge übernimmt.
  • Bei dem Bezeichner read ist der Ergebnistyp der Methode identisch mit dem Typ der Eigenschaft.
  • Bei dem Bezeichner write müssen Sie einen zusätzlichen Wert oder const-Parameter mit demselben Typ wie die Eigenschaft hinzufügen.

Indexbezeichner

Mithilfe von Indexbezeichnern können mehrere Eigenschaften dieselbe Zugriffsmethode verwenden, auch wenn sie unterschiedliche Werte repräsentieren. Indexbezeichner bestehen aus der Direktive "index" und einem Integerwert zwischen –2147483647 und 2147483647. Zum Beispiel:

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};
};

Bei Eigenschaften mit Indexbezeichnern muss auf die Bezeichner read und write eine Methode (kein Feld) folgen. Eine Zugriffsmethode für eine Eigenschaft mit einem Indexbezeichner benötigt einen zusätzlichen Wert-Parameter mit dem Typ Integer.

  • Bei einer read-Funktion muss dies der letzte Parameter sein.
  • Bei einer write-Prozedur muss dies der vorletzte Parameter sein (vor dem Parameter, der den Eigenschaftswert angibt).

Die Integerkonstante wird beim Zugriff auf die Eigenschaft automatisch an die Zugriffsmethode übergeben.

Warnung: Die standardmäßigen Indexeigenschaften von Delphi stehen im Konflikt mit dem Indizierungsoperator von C++. Deshalb werden alle standardmäßigen Indexeigenschaften von Delphi als Elementoperator [](int index) bereitgestellt, die die zugrunde liegende Eigenschaft weitergeben.

Speicherbezeichner

Die optionalen Direktiven stored, default und nodefault, die sogenannten Speicherbezeichner, wirken sich nicht auf das Verhalten des Programms aus, sondern steuern, ob die Werte der published-Eigenschaften in Formulardateien gespeichert werden oder nicht. Streaming-VCL- und FMX-Steuerelemente werden von der RTL verwaltet. Verwenden Sie diese Parameter bei der Entwicklung von VCL- oder FMX-Komponenten.

Stored

Diese Direktive wird vom Formular-Streaming-System verwendet. Standardmäßig werden alle Eigenschaften in eine .dfm-Datei gestreamt. Dies ermöglicht es, eine Eigenschaft bedingt oder immer aus dem Stream auszuschließen. Sie können nicht erzwingen, dass eine Eigenschaft gestreamt wird, indem Sie "stored" auf true festlegen. Sie können lediglich erzwingen, dass eine Eigenschaft nicht gestreamt wird, indem Sie "stored" auf false festlegen.

Nach der Direktive stored muss der Wert true oder false, der Name eines booleschen Feldes oder der Name einer parameterlosen Methode folgen, die einen booleschen Wert zurückgibt.

stored = [Boolean]

Wenn eine Eigenschaft ohne die Direktive "stored" deklariert, wird sie standardmäßig als true behandelt.

Default und nodefault

Mit der Direktive default können Sie den Standardwert einer Eigenschaft festlegen. Dieser wird vom Objektinspektor verwendet, um die Eigenschaft anders darzustellen, wenn der Wert nicht der Standardwert ist. Eigenschaften werden ebenfalls nicht gestreamt, wenn ihr Wert mit dem Standardwert übereinstimmt.

Bitte beachten Sie, dass durch die Angabe des Standardwerts die Eigenschaft nicht initialisiert wird. Sie müssen Felder (für Eigenschaften, die von Feldern unterstützt werden) im Konstruktor Ihrer Klasse initialisieren. Wenn Sie den Wert ändern, mit dem ein Feld initialisiert wird, das eine Eigenschaft unterstützt, sollten Sie auch den vom Schlüsselwort "default" verwendeten Wert aktualisieren. Es gibt keine Compiler-Warnung, um den Wert dieser Direktive mit einem von der Eigenschaft verwendeten Feld abzugleichen.

Nach default muss eine Konstante angegeben werden, die denselben Typ wie die Eigenschaft hat.

default = _ (value of the same type)
nodefault

Die Direktive nodefault wird verwendet, um einen gerbten default-Wert zu überschreiben, ohne einen neuen anzugeben. Wenn Sie beim Festlegen einer Eigenschaft keinen Standardwert angeben, hat sie keinen Standardwert. Dies wird für Neudeklarationen von Eigenschaften verwendet, bei denen der Vorfahre einen Standardwert hatte und Sie diesen zurücksetzen möchten.

Die Direktiven default und nodefault unterstützen nur Ordinal- und Mengentypen, vorausgesetzt, dass die Ober- und Untergrenzen der Menge einen Ordinalwert zwischen 0 und 31 haben. Wenn eine Eigenschaft nicht als default oder nodefault deklariert ist, gilt sie als nodefault. Für Gleitkommatypen, Zeiger und Strings gilt der implizite Standardwert 0, nullptr bzw. (leerer String).

Hinweis: Sie können den Ordinalwert –2147483648 nicht als Standardwert verwenden. Dieser Wert wird intern für die Darstellung von nodefault verwendet.

stored und default/nodefault und Streaming-Eigenschaften

Diese Bezeichner werden zum Streamen einer Klasse verwendet. Wenn sich der aktuelle Wert einer Eigenschaft von ihrem default-Wert unterscheidet (oder kein default-Wert vorhanden ist, entweder weil default nicht oder weil nodefault angegeben ist) oder wenn der Bezeichner "stored" "true" ist, wird der Wert der Eigenschaft beim Streamen gespeichert. Andernfalls wird er nicht gespeichert (nicht geschrieben).

Warnung: Bei Array-Eigenschaften werden Speicherbezeichner nicht unterstützt. Bei der Deklaration von Array-Eigenschaften hat die Direktive "default" eine andere Bedeutung. Siehe "Array-Eigenschaften" weiter oben.

implements-Attribute

C++Builder führt das implements-Attribut für das Schlüsselwort __property ein. Das Attribut "implements" ermöglicht die effektive Implementierung von Interfaces ohne Mehrfachvererbung.

Das Attribut implements des C++-Schlüsselwortes __property ermöglicht die Implementierung eines Interface, indem es als Attribut oder Feld einer Klasse angegeben wird. Diese Implementierung entspricht der Delphi-Direktive "implements", die einer Klasse die Implementierung eines Interface durch Delegieren an eine Eigenschaft ermöglicht. In einer __property-Anweisung wird das Attribut "implements" wie das Attribut nodefault an die letzte Stelle gesetzt.

Um weitere Informationen über "implements"-Attribute zu erhalten, klicken Sie hier.

Eigenschaften überschreiben und neu deklarieren

Eine Deklaration einer Eigenschaft ohne Angabe eines Typs wird als Eigenschaftsüberschreibung bezeichnet. Eigenschaftsüberschreibungen ermöglichen das Ändern der geerbten Sichtbarkeit bzw. des geerbten Bezeichners einer Eigenschaft.

In der einfachsten Form braucht nur das reservierte Wort __property gefolgt von einem geerbten Eigenschaftsbezeichner angegeben zu werden. Auf diese Weise wird die Sichtbarkeit der betreffenden Eigenschaft geändert.</br> So kann beispielsweise eine in einer Vorfahrklasse als protected deklarierte Eigenschaft im public- oder published-Abschnitt der Klasse neu deklariert werden. Eigenschaftsüberschreibungen können die Direktiven read, write, stored, default und nodefault enthalten, durch die die entsprechende geerbte Direktive überschrieben wird.

Mithilfe von Überschreibungen können Sie geerbte Zugriffsbezeichner ersetzen, fehlende Bezeichner hinzufügen oder die Sichtbarkeit einer Eigenschaft erweitern, jedoch keine Zugriffsbezeichner entfernen oder die Sichtbarkeit verringern. Optional kann auch mit der Direktive implements die Liste der implementierten Interfaces ergänzt werden, ohne die geerbten Interfaces zu entfernen.

Das folgende Beispiel deklariert die Eigenschaft Left mit der Sichtbarkeit __published neu. In der Vorfahrklasse war diese Eigenschaft mit der Sichtbarkeit "public" oder "protected" deklariert:

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

Wenn Sie beim Neudeklarieren einer Eigenschaft einen Typbezeichner angeben, wird die geerbte Eigenschaft nicht überschrieben, sondern lediglich verborgen. Dadurch wird eine neue Eigenschaft mit demselben Namen wie die geerbte erstellt. Jede Eigenschaftsdeklaration, in der ein Typ angegeben ist, muss immer vollständig sein und es muss zumindest ein Zugriffsbezeichner vorhanden sein.

Beispiele für __property

Das folgende Beispiel zeigt einige einfache Eigenschaftsdeklarationen:

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};

};

Dieses Beispiel zeigt mehrere Eigenschaftsdeklarationen:

  • Die Eigenschaft X verfügt durch die Member-Funktionen readX bzw. writeX über Lese-Schreib-Zugriff.
  • Die Eigenschaft Y ist direkt der Member-Variable Fy zugeordnet und ist schreibgeschützt.
  • Die Eigenschaft Z ist ein schreibgeschützter Wert, der berechnet wird; sie wird nicht als Daten-Member in der Klasse gespeichert.
  • Die Eigenschaft Cells demonstriert eine Array-Eigenschaft mit zwei Indizes.

Das nächste Beispiel zeigt, wie auf diese Eigenschaften im Quelltext zugegriffen wird:

// 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);
}

Compiler-Unterstützung

Die Unterstützung für das Schlüsselwort __property variiert je nach dem verwendeten C++-Compiler, Clang (empfohlen) oder klassisch. Insbesondere verarbeitet die __property-Implementierung in Clang-Compilern einige der Features, die der klassische bcc32 nicht verarbeitet.

Der Hauptunterschied kann anhand des folgenden Beispiels erklärt werden:

#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();
}

Im Code ruft der klassische bcc32-Compiler die Getter-Methode auf und arbeitet mit dem zurückgegebenen temporären Ergebnis. Die Clang-Compiler führen dasselbe aus, rufen aber auch die Setter-Methode mit diesem Ergebnis auf. Das heißt, dass nur der Clang-Compiler Code generiert, der sich so verhält, wie ein Entwickler intuitiv erwartet, dass sich die +=-Zeile verhält.

Siehe auch