Méthodes de classe

De RAD Studio
Aller à : navigation, rechercher

Remonter à Classes - Index

Dans C++Builder, une méthode de classe est une méthode qui peut être invoquée sur un nom de classe, ainsi que sur une instance de cette classe. En revanche, les méthodes d'objet peuvent être invoquées seulement sur les objets -- instances d'une classe.

Comment les méthodes de classe fonctionnent dans Delphi

Le langage Delphi (Object Pascal) prend en charge les méthodes de classe et une métaclasse comme décrit dans la section Méthodes de classe de la rubrique d'aide Delphi Méthodes (Delphi).

En voici un exemple :

 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;

Méthodes de classe en tant que méthodes statiques avec un paramètre TMetaClass* explicite

Avant C++Builder 2009, les méthodes de classe étaient représentées comme des méthodes statiques avec un paramètre métaclasse explicite. Une métaclasse est représentée par un pointeur sur une instance de TMetaClass ou un TClass. Cette métaclasse ou référence de classe est obtenue avec l'extension __classid qui renvoie une instance de TMetaClass* pour un nom de classe.

Voici un exemple de cet usage, tentant de définir les mêmes méthodes et propriétés que dans l'exemple Delphi ci-dessus. Notez que certaines fonctionnalités de méthodes de classe Delphi ne peuvent pas être réalisées correctement à l'aide de cette approche de métaclasse.

 // 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);

Il existe plusieurs limitations avec cette façon d'exposer les méthodes de classe en utilisant un paramètre TMetaClass* explicite :

  • Le code C++ ne peut pas invoquer correctement les méthodes de classe virtuelles. Les appels C++ devraient passer deux paramètres cachés : le pointeur à l'instance de l'objet et le paramètre <span class="code">TMetaClass*</span> explicite. Toutefois, la fonction attend seulement un paramètre TMetaClass*.
  • Même dans les cas où l'appel à une méthode de classe virtuelle réussit, le code C++ doit avoir une instance de l'objet pour invoquer la méthode, mais une méthode de classe adéquate devrait être invocable sans nécessiter une instance d'objet.
  • Le code C++ ne peut pas accéder convenablement aux propriétés dont les getters ou setters sont des méthodes de classe, car il n'existe aucun moyen de fournir le paramètre TMetaClass* qui doit être explicite.

Méthodes de classe utilisant le mot clé __classmethod

C++Builder 2009 introduit les méthodes de classe qui retirent les limitations listées ci-dessus et fournissent une syntaxe plus simple et plus intuitive pour les méthodes de classe. Les méthodes de classe sont maintenant déclarées avec le nouveau mot clé __classmethod.

Voici comment le code ci-dessus est écrit avec la syntaxe __classmethod :

 // 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 ne fournit pas un moyen de déclarer une reférence de classe autrement que par TMetaClass*. Les méthodes de classe sont invoquées avec un nom de type ou avec une instance d'un objet. Les méthodes de classes virtuelles sont invoquées de la même façon que les méthodes virtuelles régulières, sauf qu'au lieu d'effectuer un push du pointeur this, le pointeur métaclasse est le paramètre caché.

Cette implémentation fournit deux capacités :

  • Les méthodes de classe peuvent être virtuelles.
  • Les propriétés de classe peuvent être définies.

Répartition dynamique des méthodes de classes virtuelles

Si une méthode de classe est définie avec __classmethod pour une classe dérivée d'une autre classe, vous ne pouvez pas invoquer dynamiquement la méthode virtuelle de la classe dérivée en utilisant un nom de classe. Toutefois, vous pouvez invoquer la méthode adéquate en utilisant une instance.

Le mécanisme "virtuel" des méthodes de classes est analogue au mécanisme "virtuel" des méthodes régulières. Pour les méthodes régulières, vous obtenez seulement le polymorphisme lors de l'utilisation d'un pointeur ou d'une référence, car la vtable est alors déterminée à l'exécution. Avec une instance de valeur, vous n'obtenez pas le polymorphisme car la vtable est déterminée à la compilation.

De même, pour les méthodes de classe, vous obtenez le polymorphisme avec une instance mais pas avec un type. Pour illustrer cela :

 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

Mise à jour de votre code afin d'utiliser __classmethod

Si vous avez du code C++ qui implémente des méthodes de classe en utilisant les méthodes de classe d'ancien style avec un paramètre Metaclass *, vous pouvez mettre à jour votre code afin d'utiliser la syntaxe __classmethod. Puisque le paramètre TMetaClass* est maintenant caché, la signature et le substantypage de telles méthodes de classe sont différents. Le tableau suivant illustre comment mettre à jour les constructions C++ communes en utilisant les méthodes de classe. Il utilise la méthode add(int i, int j) et la propriété Count de l'exemple de code __classmethod ci-dessus pour illustrer les modifications que vous devriez faire.

Objet du code Code "class static" d'ancien style Code mis à jour avec __classmethod

Déclaration de fonction

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);
};

Utilisation du __classid

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

Utilisation du __classid dérivé

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

Utilisation d'une instance de classe

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

Utilisation d'une instance dérivée

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

Utilisation d'une propriété de classe

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

Voir aussi