__property
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.
Inhaltsverzeichnis
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 vonread
-,write
-,stored
-,index
-,default
- (odernondefault
-) oderimplements
-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
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
- undwrite
-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.
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).
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).
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.