Méthodes de classe

De Appmethod Topics
Aller à : navigation, rechercher

Remonter à Classes - Index

Dans Appmethod C++, 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 Object Pascal

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

En voici un exemple :

TTest = class;

// Déclaration du type métaclasse
TTestClass = class of TTest;

TTest = class
public
  // Méthodes de classe
  class function add(I, J: Integer): Integer;
  class function GetClsName: string;

  // Méthode de classe virtuelle
  class function GetClsNameVirt: string; virtual;

  // Getter et setter de classe
  class function GetCount: Integer;
  class procedure SetCount(I: Integer);

  // Getter et setter de classe virtuelle
  class function GetStrProp: string; virtual;
  class procedure SetStrProp(N: string); virtual;

  // Classe statique
  class function GetStaticCount: Integer; static;
  class procedure SetStaticCount(I: Integer); static;

  // Propriétés de classe
  property Count: Integer read GetCount write SetCount; // Non-virtuel
  property StrProp: string read GetStrProp write SetStrProp; // getters/setters virtuels
end;

// Fonction qui prend une référence de classe
function RegisterTestType(Cls: TTestClass): boolean;

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

Avant Appmethod C++ 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 Object Pascal ci-dessus. Notez que certaines fonctionnalités de méthodes de classe Object Pascal ne peuvent pas être réalisées correctement à l'aide de cette approche de métaclasse.

 // Tous les types métaclasse sont TMetaClass* en C++
 typedef TMetaClass* TTestClass;
 
 class DELPHICLASS TTest;
 class PASCALIMPLEMENTATION TTest : public System::TObject
 {
   typedef System::TObject inherited;
   public:
       // Méthodes de classe exposées comme des méthodes statiques avec
       // la référence de classe 'cachée' explicite comme premier paramètre.
       static int __fastcall add(TMetaClass* vmt, int I, int J);
       static UnicodeString __fastcall GetClsName(TMetaClass* vmt);
 
       // Les méthodes de classe virtuelles seraient exposées comme des méthodes virtuelles simples
       // avec la référence de classe cachée explicite comme premier paramètre.
       // Cela signifie que sur l'appel de cette méthode depuis C++, il y aurait
       // deux paramètres 'cachés' passés--qui ne devraient pas fonctionner.
       virtual UnicodeString __fastcall GetClsNameVirt(TMetaClass* vmt);
 
       // Les méthodes non virtuelles peuvent fonctionner. Ces deux méthodes
       // sont généralement surchargées avec le premier paramètre TMetaClass*
       // codé en dur dans __classid(TTest).
       static int __fastcall GetCount(TMetaClass* vmt);
       static void __fastcall SetCount(TMetaClass* vmt, int I);
 
       // Vous pouvez surcharger ces setters et getters virtuels, mais étant donné
       // que l'appel est incorrect, le programme échoue
       // lors de l'accès à la propriété liée à ces méthodes.
       virtual UnicodeString __fastcall GetStrProp(TMetaClass* vmt);
       virtual void __fastcall SetStrProp(TMetaClass* vmt, UnicodeString N);
 
       // La méthode statique de classe Delphi serait statique C++ simple.
       static int __fastcall GetStaticCount();
       static void __fastcall SetStaticCount(int I);
 
       // Bien que le compilateur permet ces déclarations,
       // car TMetaClass* est requis, vous obtiendrez une erreur
       // du compilateur C++ sur la tentative d'accès à ces propriétés.
       __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 TMetaClass* 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

Appmethod C++ 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 :

 // Les références de classe utilisent toujours TMetaClass*.
 typedef TMetaClass* TTestClass;
 
 class DELPHICLASS TTest;
 class PASCALIMPLEMENTATION TTest : public System::TObject
 {
   typedef System::TObject inherited;
 
   public:
       // Le paramètre TMetaClass* est maintenant caché.
       // Le mot clé __classmethod marque les méthodes comme des méthodes de classe.
       __classmethod int __fastcall add(int I, int J);
       __classmethod UnicodeString __fastcall GetClsName();
 
       // Les méthodes virtuelles peuvent être utilisées.
       __classmethod virtual UnicodeString __fastcall GetClsNameVirt();
 
       // Les méthodes peuvent accéder aux propriétés de classe
       __classmethod int __fastcall GetCount();
       __classmethod void __fastcall SetCount(int I);
       __classmethod virtual UnicodeString __fastcall GetStrProp();
       __classmethod virtual void __fastcall SetStrProp(UnicodeString N);
 
       // Les méthodes statiques de classe sont toujours mappées en méthodes statiques C++.
       static int __fastcall GetstaticCount();
       static void __fastcall SetstaticCount(int I);
 
       // Propriétés de classe
       __property int Count = {read=GetCount, write=SetCount, nodefault};
       __property UnicodeString StrProp = {read=GetStrProp, write=SetStrProp};
 };

Appmethod C++ 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();
 };
 
 // Méthodes virtuelles régulières
 TDerived d;
 d.vfunc();   // appelle TDerived::vfunc;
 
 TBase* bp = new TDerived();
 bp->vfunc(); // appelle TDerived::vfunc;
 
 TBase& br = TDerived();
 br.vfunc();  // appelle TDerived::vfunc;
 
 TBase b;
 b.vfunc();   // appelle TBase::vfunc
 // Méthodes virtuelles de classe
 TBase* b = new TDerived();
 b->cfunc();         // appelle version dans TDerived--dynamic
 TDerived::cfunc();  // appelle version dans TDerived--temps de compilation--non dynamique
 __classid(TDerived)->cfunc();  // erreur du compilateur

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; // Utilisation du nom de classe
 // Utilisation d'une instance
 TTest* p = new TTest();
 ...
 p->Count = 20;



Voir aussi