Klassenmethoden

Aus RAD Studio
Wechseln zu: Navigation, Suche

Nach oben zu Klassen - Index

In C++Builder ist eine Klassenmethode eine Methode, die für einen Klassennamen und für eine Instanz dieser Klasse aufgerufen werden kann. Im Gegensatz dazu können Objektmethoden nur für Objekte – Instanzen einer Klasse – aufgerufen werden.

Arbeitsweise von Klassenmethoden in Delphi

Die Delphi-Sprache (Object Pascal) unterstützt Klassenmethoden und eine Metaklasse (siehe den Abschnitt Klassenmethoden im Delphi-Hilfethema Methoden).

Hier ist ein Beispiel dafür:

 TTest = class;
 
 // Deklaration des Metaklassentyps
 TTestClass = class of TTest;
 
 TTest = class
 public
   // Klassenmethoden
   class function add(I, J: Integer): Integer;
   class function GetClsName: string;
 
   // Virtuelle Klassenmethode
   class function GetClsNameVirt: string; virtual;
 
   // Getter und Setter für die Klasse
   class function GetCount: Integer;
   class procedure SetCount(I: Integer);
 
   // Virtuelle Getter und Setter für die Klasse
   class function GetStrProp: string; virtual;
   class procedure SetStrProp(N: string); virtual;
 
   // Statische Klasse
   class function GetStaticCount: Integer; static;
   class procedure SetStaticCount(I: Integer); static;
 
   // Klasseneigenschaften
   property Count: Integer read GetCount write SetCount; // Nicht virtuell
   property StrProp: string read GetStrProp write SetStrProp; // Virtuelle g/setters
 end;
 
 // Funktion, die eine Klassenreferenz übernimmt
 function RegisterTestType(Cls: TTestClass): boolean;

Klassenmethoden als statische Methoden mit explizitem Parameter TMetaClass*

Vor C++Builder 2009 wurden Klassenmethoden als statische Methoden mit einem expliziten Metaklassenparameter repräsentiert. Eine Metaklasse wird durch einen Zeiger auf eine TMetaClass-Instanz oder eine TClass repräsentiert. Diese Metaklasse oder Klassenreferenz wird mit der Erweiterung __classid ermittelt, die eine TMetaClass*-Instanz für einen Klassennamen zurückgibt.

Hier ist ein Beispiel dieser Verwendung. Das Beispiel versucht, dieselben Methoden und Eigenschaften wie in dem obigen Delphi-Beispiel zu definieren. Beachten Sie bitte, dass einige Funktionen von Delphi-Klassenmethoden mit diesem Metaklassenzugang nicht korrekt definiert werden können.

 // Alle Metaklassentypen sind in C++ TMetaClass*
 typedef TMetaClass* TTestClass;
 
 class DELPHICLASS TTest;
 class PASCALIMPLEMENTATION TTest : public System::TObject
 {
   typedef System::TObject inherited;
   public:
       // Als statische Methoden 'verborgen' bereitgestellte Klassenmethoden
       // Klassenreferenz explizit als der erste Parameter.
       static int __fastcall add(TMetaClass* vmt, int I, int J);
       static UnicodeString __fastcall GetClsName(TMetaClass* vmt);
 
       // Virtuelle Klassenmethoden würden als rein virtuelle Methoden mit der
       // verborgenen Klassenreferenz explizit als der erste Parameter bereitgestellt.
       // Das bedeutet, dass beim Aufruf dieser Methode aus C++ zwei 'verborgene'
       // Parameter übergeben werden müssten -- was nicht funktionieren würde.
       virtual UnicodeString __fastcall GetClsNameVirt(TMetaClass* vmt);
 
       // Mit nicht virtuellen Methoden kann leichter gearbeitet werden. Diese beiden Methoden
       // sind typischerweise überladen mit dem ersten TMetaClass*-Parameter,
       // der als __classid(TTest) hart-codiert ist.
       static int __fastcall GetCount(TMetaClass* vmt);
       static void __fastcall SetCount(TMetaClass* vmt, int I);
 
       // Sie können diese virtuellen Setter und Getter überladen, aber falls der
       // Aufruf nicht korrekt ist, schlägt das Programm beim Zugriff
       // auf die an diese Methoden gebundene Eigenschaft fehl.
       virtual UnicodeString __fastcall GetStrProp(TMetaClass* vmt);
       virtual void __fastcall SetStrProp(TMetaClass* vmt, UnicodeString N);
 
       // Statische Delphi-Klassenmethoden würden rein C++-statisch sein.
       static int __fastcall GetStaticCount();
       static void __fastcall SetStaticCount(int I);
 
       // Obwohl der Compiler diese Deklarationen zulässt,
       // weil TMetaClass* erforderlich ist, würden Sie einen Fehler
       // vom C++-Compiler beim Versuch erhalten, auf diese Eigenschaften zuzugreifen.
       __property int Count = {read=GetCount, write=SetCount, nodefault};
       __property UnicodeString StrProp = {read=GetStrProp, write=SetStrProp};};
 
 extern PACKAGE bool __fastcall RegisterTestType(TMetaClass* Cls);

Es gibt einige Einschränkungen für diese Art der Bereitstellung von Klassenmethoden mit einem expliziten TMetaClass*-Parameter:

  • C++-Code kann virtuelle Klassenmethoden nicht korrekt aufrufen. C++-Aufrufe müssten zwei verborgene Parameter übergeben: den Zeiger auf die Instanz des Objekts und den expliziten TMetaClass*-Parameter. Die Funktion erwartet jedoch nur einen TMetaClass*-Parameter.
  • Sogar in Fällen, wo der Aufruf einer virtuellen Klassenmethode erfolgreich ist, muss der C++-Code für den Aufruf der Methode über eine Instanz des Objekts verfügen, eine korrekte Klassenmethode sollte aber ohne eine Objektinstanz aufgerufen werden können.
  • C++-Code kann nicht korrekt auf Eigenschaften zugreifen, deren Getter oder Setter Klassenmethoden sind, weil es keine Möglichkeit gibt, den Parameter TMetaClass*, der explizit sein muss, bereitzustellen.

Klassenmethoden, die das Schlüsselwort __classmethod verwenden

C++Builder 2009 führt Klassenmethoden ein, die die obigen Einschränkungen aufheben und eine einfachere und intuitivere Syntax für Klassenmethoden bereitstellen. Klassenmethoden werden jetzt mit dem neuen Schlüsselwort __classmethod deklariert.

Im Folgenden finden Sie den obigen Code mit der __classmethod-Syntax:

 // Klassenreferenzen verwenden weiterhin TMetaClass*.
 typedef TMetaClass* TTestClass;
 
 class DELPHICLASS TTest;
 class PASCALIMPLEMENTATION TTest : public System::TObject
 {
   typedef System::TObject inherited;
 
   public:
       // Der Parameter TMetaClass* ist nun verborgen.
       // Das Schlüsselwort __classmethod kennzeichnet Methoden als Klassenmethoden.
       __classmethod int __fastcall add(int I, int J);
       __classmethod UnicodeString __fastcall GetClsName();
 
       // Virtuelle Methoden können verwendet werden.
       __classmethod virtual UnicodeString __fastcall GetClsNameVirt();
 
       // Methoden können auf Klasseneigenschaften zugreifen
       __classmethod int __fastcall GetCount();
       __classmethod void __fastcall SetCount(int I);
       __classmethod virtual UnicodeString __fastcall GetStrProp();
       __classmethod virtual void __fastcall SetStrProp(UnicodeString N);
 
       // Statische Klassenmethoden werden weiterhin zu statischen C++Methoden zugeordnet.
       static int __fastcall GetstaticCount();
       static void __fastcall SetstaticCount(int I);
 
       // Klasseneigenschaften
       __property int Count = {read=GetCount, write=SetCount, nodefault};
       __property UnicodeString StrProp = {read=GetStrProp, write=SetStrProp};
 };

C++Builder stellt keine Möglichkeit bereit, eine Klassenreferenz anders als mit TMetaClass* zu deklarieren. Daher werden Klassenmethoden mit einem Typnamen oder mit einer Instanz eines Objekts aufgerufen. Virtuelle Klassenmethoden werden genauso wie normale virtuelle Methoden aufgerufen, außer, dass anstelle der Verschiebung des this-Zeigers der Metaklassenzeiger der verborgene Parameter ist.

Diese Implementierung liefert Folgendes:

  • Klassenmethoden können virtuell sein.
  • Klasseneigenschaften können definiert werden.

Dynamische Weitergabe von virtuellen Klassenmethoden

Wenn eine Klassenmethode mit __classmethod für eine von einer anderen Klasse abgeleiteten Klasse definiert wurde, können Sie die virtuelle Methode der abgeleiteten Klasse nicht dynamisch mit einem Klassennamen aufrufen. Sie können aber die korrekte Methode mithilfe einer Instanz aufrufen.

Der "virtuelle" Mechanismus von Klassenmethoden ist dem für normale Methoden ähnlich. Bei normalen Methoden erzielen Sie Polymorphismus nur, wenn Sie einen Zeiger oder eine Referenz verwenden, weil die V-Tabelle dann zur Laufzeit festgelegt wird. Bei einer Werteinstanz können Sie keinen Polymorphismus erreichen, weil die V-Tabelle zur Compilierzeit festgelegt wird.

Ähnlich verhält es sich bei Klassenmethoden: Sie erhalten Polymorphismus mit einer Instanz aber nicht mit einem Typ. Ein Beispiel:

 class TBase {
     virtual __classmethod void cfunc();
     virtual void vfunc();
 };
 
 class TDerived: TBase {
     virtual __classmethod void cfunc();
     virtual void vfunc();
 };
 
 // Normale virtuelle Methoden
 TDerived d;
 d.vfunc();   //ruft TDerived::vfunc; auf
 
 TBase* bp = new TDerived();
 bp->vfunc(); //ruft TDerived::vfunc; auf
 
 TBase& br = TDerived();
 br.vfunc();  //ruft TDerived::vfunc; auf
 
 TBase b;
 b.vfunc();   //ruft TBase::vfunc auf
 // Virtuelle Klassenmethoden
 TBase* b = new TDerived();
 b->cfunc();         //ruft die Version in TDerived -- dynamisch auf
 TDerived::cfunc();  //ruft die Version in TDerived auf -- Compilierzeit -- nicht dynamisch
 __classid(TDerived)->cfunc();  //Compiler-Fehler

Aktualisierung des Quelltextes für die Verwendung von __classmethod

Wenn Ihr C++-Quelltext Klassenmethoden mithilfe des alten Metaklassen-*-Parameter implementiert, können Sie Ihren Quelltext für die Verwendung der __classmethod-Syntax aktualisieren. Da der Parameter TMetaClass* jetzt verborgen ist, sind die Signatur und die Verkürzung solcher Klassenmethoden unterschiedlich. Die folgende Tabelle illustriert, wie gebräuchliche C++-Konstrukte mit Klassenmethoden aktualisiert werden können. Die Tabelle verwendet die Methode add(int i, int j) und die Eigenschaft Count aus dem obigen __classmethod-Codebeispiel, um die Änderungen, die Sie vornehmen müssten, darzustellen.

Zweck des Codes Code im alten “statischen Klassen”-Stil Mit __classmethod aktualisierter Code

Funktionsdeklaration

class TTest : public System::TObject {
 public:
 static int __fastcall add(TMetaClass* vmt, int I, int J);
};
class TTest : public System::TObject {
public:
__classmethod int __fastcall add(int I, int J);
};

Verwendung von __classid

TTest::add(__classid(TTest), 10, 20);
TTest::add(10, 20);

Verwendung des abgeleiteten __classid

TTestDerived::add(__classid(TTestDerived), 10, 20);
TTestDerived::add(10, 20);

Verwendung der Klasseninstanz

TTest* p = new TTest();
// …
TTest::add(p->ClassType(), 10, 20);
TTest* p = new TTest();
// …
p->add(10, 20);

Verwendung der abgeleiteten Instanz

TTest* p = new TTestDerived();
// …
TTest::add(p->ClassType(), 10, 20);
TTest* p = new TTestDerived();
// …
p->add(10, 20);

Verwendung der Klasseneigenschaft

TTest* p = new TTest();
// …
p->Count = 20;
TTest::Count = 20; //Using class name
// Using instance
TTest* p = new TTest();
// …
p->Count = 20;

Siehe auch