Class Methods

From RAD Studio
Jump to: navigation, search

Go Up to Classes Index

In C++, 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.

How Class Methods Work in Delphi

The Delphi language (Object Pascal) supports class methods and a metaclass as described in the Class Methods section in the Delphi Methods (Delphi) help topic .

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;

Class Methods as Static Methods with Explicit TMetaClass* Parameter

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++ code cannot properly invoke virtual class methods. C++ calls would have to pass two hidden this parameters: the pointer to the instance of the object and the explicit TMetaClass* parameter. However, the function expects only one TMetaClass* parameter.
  • Even in cases where the call to a virtual class method succeeds, the C++ code must have an instance of the object to invoke the method, but a proper class method should be invokable without requiring an object instance.
  • C++ code cannot properly access properties whose getters or setters are class methods, because there's no way to provide the TMetaClass* parameter, which must be explicit.

Class Methods Using __classmethod Keyword

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:

  • Class methods can be virtual.
  • Class properties can be defined.

Dynamic Dispatch of Virtual Class Methods

If you have a class method defined with __classmethod for a class that is derived from another class, you cannot 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 do not get polymorphism because the vtable is determined 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

Updating Your Code to Use __classmethod

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;

See Also