RAD Studio
ContentsIndex
PreviousUpNext
Class Methods

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++ 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.

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

 

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;  
Copyright(C) 2009 Embarcadero Technologies, Inc. All Rights Reserved.
What do you think about this topic? Send feedback!