In C++Builder, a class method is a method that can be invoked on a class name, as well as on an instance of that class. In contrast, object methods can be invoked only on objects -- instances of a class.
The Delphi language (Object Pascal) supports class methods and a metaclass as described in the Class Methods section in the Delphi help topic Methods.
Here is a sample:
TTest = class; // Declaring metaclass type TTestClass = class of TTest; TTest = class public // Class methods class function add(I, J: Integer): Integer; class function GetClsName: string; // Virtual class method class function GetClsNameVirt: string; virtual; // Class getter and setter class function GetCount: Integer; class procedure SetCount(I: Integer); // Virtual class getter and setter class function GetStrProp: string; virtual; class procedure SetStrProp(N: string); virtual; // Class static class function GetStaticCount: Integer; static; class procedure SetStaticCount(I: Integer); static; // Class properties property Count: Integer read GetCount write SetCount; // Non-virtual property StrProp: string read GetStrProp write SetStrProp; // Virtual g/setters end; // Function that takes a class reference function RegisterTestType(Cls: TTestClass): boolean;
Prior to C++Builder 2009, class methods were represented as static methods with an explicit metaclass parameter. A metaclass is represented by a pointer to a TMetaClass instance or a TClass. This metaclass or class reference is obtained with the __classid extension, which returns a TMetaClass* instance for a class name.
Here is an example of this usage, attempting to define the same methods and properties as in the Delphi sample above. Note that some of the Delphi class method features cannot be done properly using this metaclass approach.
// All metaclass types are TMetaClass* in C++ typedef TMetaClass* TTestClass; class DELPHICLASS TTest; class PASCALIMPLEMENTATION TTest : public System::TObject { typedef System::TObject inherited; public: // Class methods exposed as static methods with the 'hidden' // class reference explicit as the first parameter. static int __fastcall add(TMetaClass* vmt, int I, int J); static UnicodeString __fastcall GetClsName(TMetaClass* vmt); // Virtual class methods would be exposed as plain virtual methods with // the hidden class reference explicit as the first parameter. // This means that upon calling this method from C++, there would have // to be two 'hidden' parameters passed in--which would not work. virtual UnicodeString __fastcall GetClsNameVirt(TMetaClass* vmt); // Non-virtual methods are feasible to work with. These two methods // are typically overloaded with the first TMetaClass* parameter // hardcoded to __classid(TTest). static int __fastcall GetCount(TMetaClass* vmt); static void __fastcall SetCount(TMetaClass* vmt, int I); // You can overload these virtual setters and getters, but given // that the call is incorrect, the program fails // when accessing the property tied to these methods. virtual UnicodeString __fastcall GetStrProp(TMetaClass* vmt); virtual void __fastcall SetStrProp(TMetaClass* vmt, UnicodeString N); // Delphi class static method would be plain C++ static. static int __fastcall GetStaticCount(); static void __fastcall SetStaticCount(int I); // Although the compiler allows these declarations, // because TMetaClass* is required, you'll get an error // from the C++ compiler upon attempting to access these properties. __property int Count = {read=GetCount, write=SetCount, nodefault}; __property UnicodeString StrProp = {read=GetStrProp, write=SetStrProp};}; extern PACKAGE bool __fastcall RegisterTestType(TMetaClass* Cls);
There are several limitations with this way of exposing class methods using an explicit TMetaClass* parameter:
C++Builder 2009 introduces class methods that remove the limitations listed above and provide a simpler and more intuitive syntax for class methods. Class methods are now declared with the new keyword __classmethod.
Here is how the code above is written using the __classmethod syntax:
// Class references still use TMetaClass*. typedef TMetaClass* TTestClass; class DELPHICLASS TTest; class PASCALIMPLEMENTATION TTest : public System::TObject { typedef System::TObject inherited; public: // The TMetaClass* parameter is now hidden. // The __classmethod keyword flags methods as class methods. __classmethod int __fastcall add(int I, int J); __classmethod UnicodeString __fastcall GetClsName(); // Virtual methods can be used. __classmethod virtual UnicodeString __fastcall GetClsNameVirt(); // Methods can access class properties __classmethod int __fastcall GetCount(); __classmethod void __fastcall SetCount(int I); __classmethod virtual UnicodeString __fastcall GetStrProp(); __classmethod virtual void __fastcall SetStrProp(UnicodeString N); // Class static methods still map to C++ static methods. static int __fastcall GetstaticCount(); static void __fastcall SetstaticCount(int I); // Class properties __property int Count = {read=GetCount, write=SetCount, nodefault}; __property UnicodeString StrProp = {read=GetStrProp, write=SetStrProp}; };
C++Builder does not provide for a way to declare a class reference other than by TMetaClass*. Hence, class methods are invoked with a typename or with an instance of a object. Virtual class methods are invoked the same way regular virtual methods are invoked, except that instead of the this pointer being pushed, the metaclass pointer is the hidden parameter.
This implementation provides two capabilities:
If you have a class method defined with __classmethod for a class that is derived from another class, you can't dynamically invoke the derived class's virtual method using a class name. However, you can invoke the proper method using an instance.
The "virtual" mechanism of class methods is analogous to virtual for regular methods. For regular methods, you get polymorphism only when using a pointer or a reference, because the vtable is then determined at run time. With a value instance, you don't get polymorphism because the vtable is determine at compile time.
Similarly, for class methods you get polymorphism with an instance but not with a type. To illustrate:
class TBase { virtual __classmethod void cfunc(); virtual void vfunc(); }; class TDerived: TBase { virtual __classmethod void cfunc(); virtual void vfunc(); }; // Regular virtual methods TDerived d; d.vfunc(); //calls TDerived::vfunc; TBase* bp = new TDerived(); bp->vfunc(); //calls TDerived::vfunc; TBase& br = TDerived(); br.vfunc(); //calls TDerived::vfunc; TBase b; b.vfunc(); //calls TBase::vfunc // Class virtual methods TBase* b = new TDerived(); b->cfunc(); //calls version in TDerived--dynamic TDerived::cfunc(); //calls version in TDerived--compile time--not dynamic __classid(TDerived)->cfunc(); //compiler error
If you have C++ code that implements class methods using the older style class methods with a Metaclass * parameter, you can update your code to use the __classmethod syntax. Since the TMetaClass* parameter is now hidden, the signature and mangling of such class methods are different. The following table illustrates how to update common C++ constructs using class methods. It uses the add(int i, int j) method and the Count property from the __classmethod code sample above to illustrate the changes you would make.
Purpose of code |
Old style “class static” code |
Updated code using __classmethod |
Function declaration |
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); }; |
Using __classid |
TTest::add(__classid(TTest), 10, 20); |
TTest::add(10, 20); |
Using derived __classid |
TTestDerived::add(__classid(TTestDerived), 10, 20); |
TTestDerived::add(10, 20); |
Using class instance |
TTest* p = new TTest(); ... TTest::add(p->ClassType(), 10, 20); |
TTest* p = new TTest(); ... p->add(10, 20); |
Using derived instance |
TTest* p = new TTestDerived(); ... TTest::add(p->ClassType(), 10, 20); |
TTest* p = new TTestDerived(); ... p->add(10, 20); |
Using class property |
TTest* p = new TTest(); ... p->Count = 20; |
TTest::Count = 20; //Using class name // Using instance TTest* p = new TTest(); ... p->Count = 20; |
Copyright(C) 2009 Embarcadero Technologies, Inc. All Rights Reserved.
|
What do you think about this topic? Send feedback!
|