Ecriture de code Object Pascal adapté à C++

De Appmethod Topics
Aller à : navigation, rechercher

Remonter à Guide du langage Object Pascal - Index


C++ peut consommer du code Object Pascal. Le compilateur en ligne de commande Object Pascal utilise les commutateurs suivants pour générer les fichiers requis par C++ pour traiter du code Object Pascal :

  • Le commutateur -JL génère les fichiers .lib, .bpi, .bpl et .obj à partir d'un fichier .dpk, et les fichiers d'en-tête pour toutes les unités du package.
  • Le commutateur -JPHNE effectue la même opération à partir d'une unité .pas.

Toutefois, les fonctionnalités de Object Pascal ne sont pas toutes adaptées à C++. Cette rubrique liste les fonctionnalités POSSIBLES et celles IMPOSSIBLES pour du code d'exécution Object Pascal que vous voulez consommer à partir de C++.

POSSIBLE

Redéclaration de tous les constructeurs hérités

A l'inverse de Object Pascal, il n'y a pas de constructeurs hérités dans C++. Par exemple, ce qui suit est incorrect :

class A
{
  public:
  A(int x) {}
};

class B: public A
{
};

int main(void)
{
  B *b = new B(5);  // Error
  delete b;
}

La logique de génération du fichier d'en-tête du compilateur Object Pascal est consciente de cette différence de langage, et elle ajoute les constructeurs hérités manquants à chaque classe dérivée. Toutefois, ces constructeurs initialisent également les variables membres de la classe. Cela génère des problèmes si une classe de base invoque une méthode virtuelle ayant déjà initialisé l'une de ces variables membres à une valeur autre que celle par défaut. Il est particulièrement important de redéclarer les constructeurs hérités si le constructeur de base peut initialiser un membre d'un type delphireturn dans la classe.

Garantie d'une signature distincte pour chaque constructeur d'une hiérarchie

C++ ne prend pas en charge les constructeurs nommés. Pour cette raison, les constructeurs surchargés ne doivent pas avoir de paramètres identiques, ni similaires. Par exemple, le code suivant ne fonctionne pas pour la consommation C++ :

MyPoint = class
public
  constructor Polar(Radius, Angle: Single);
  constructor Rect(X, Y: Single);

Le code C++ suivant génèrera des erreurs de compilation associées aux constructeurs dupliqués :

class PASCALIMPLEMENTATION MyPoint : public System::TObject
{
  public:
    __fastcall MyPoint(float Radius, float Angle);
    __fastcall MyPoint(float X, float Y);
};

Vous pouvez résoudre ce problème de deux manières :

  • Ajoutez un paramètre factice avec une valeur par défaut sur l'un des constructeurs. La logique de génération du fichier d'en-tête omet intentionnellement la valeur par défaut sur le constructeur, afin que les deux constructeurs soient distincts dans C++ :
MyPoint = class
public
  constructor Polar(Radius, Angle: Single);
  constructor Rect(X, Y: Single; Dummy: Integer = 0);
class PASCALIMPLEMENTATION MyPoint : public System::TObject
{
  public:
    __fastcall MyPoint(float Radius, float Angle);
    __fastcall MyPoint(float X, float Y, int Dummy);
};
  • Utilisez l'idiome Named Constructor Idiom (EN). Cette technique déclare des membres de fabrique statique de classes à la place de constructeurs nommés, lorsque les constructeurs sont surchargés avec des paramètres identiques ou similaires. Cela est particulièrement approprié pour le type enregistrement de Object Pascal. L'exemple suivant représente une solution basée sur cette technique :
class MyPoint {
public:
  static MyPoint Rect(float X, float Y);  // Rectangular coordinates
  static MyPoint Polar(float Radius, float Angle);  // Polar coordinates
private:
  MyPoint(float X, float Y);  // Rectangular coordinates
  float X_, Y_;
};

inline MyPoint::MyPoint(float X, float Y)
  : X_(X), Y_(Y) { }

inline MyPoint MyPoint::Rect(float X, float Y)
{  return MyPoint(X, Y); }

inline MyPoint MyPoint::Polar(float Radius, float Angle)
{  return Point(Radius*std::cos(Angle), Radius*std::sin(Angle)); }

IMPOSSIBLE

Surcharge des propriétés d'index

Object Pascal permet la surcharge des propriétés d'index, comme :

TTest = class
  function  GetPropI(Index: Integer): Longint; overload;
  procedure SetProp(Index: Integer; Value: Longint); overload;
  function  GetPropS(Index: Integer): String; overload;
  procedure SetProp(Index: Integer; Value: String); overload;
public
  property Props[Index: Integer] : Longint read GetPropI write SetProp;
  property Props[Index: Integer] : String read GetPropS write SetProp; default;
end;

Toutefois, l'interface résultante dans les fichiers d'en-tête ne fonctionne pas dans C++, puisque chaque propriété d'une classe doit être unique.

Appel de méthodes virtuelles depuis des constructeurs

Cette fonctionnalité est associée à la redéclaration de tous les constructeurs hérités. Pour les classes de style Object Pascal, la vtable de la classe la plus dérivée est définie lorsque les constructeurs de base sont invoqués. Cela permet au mécanisme virtuel de fonctionner à partir des constructeurs. Toutefois, cela implique un comportement étrange dans un environnement C++, tel qu'une méthode virtuelle d'une classe qui est invoquée avant l'exécution du constructeur de la classe ; ou le constructeur d'une classe qui annule l'initialisation d'un membre ayant été effectuée à partir d'un constructeur de base.

Utilisation de génériques dans les alias

C++ peut utiliser un alias Object Pascal sur un type de modèle instancié. Toutefois, C++ ne peut pas utiliser un alias Object Pascal avec des types dépendants. Le code suivant illustre ce fait :

type
  GoodArray = TArray<Integer>;
  BadArray<T> = array of T;

GoodArray est un type concret que C++ peut utiliser. En revanche, BadArray contient un type dépendant. Par conséquent, C++ ne peut pas l'utiliser.

Utilisation de génériques dans les clôtures

Les informations RTTI générées pour les événements publiés permettent à l'EDI de générer des gestionnaires d'événement. La logique de l'EDI est incapable de traiter les informations RTTI générées pour les génériques lorsque des gestionnaires d'événement C++ sont générés. Il est ainsi recommandé d'éviter l'utilisation de génériques dans les clôtures.

Utilisation d'enregistrements avec les constructeurs

Dans Object Pascal, un enregistrement variant est équivalent à une union C++. Les enregistrements avec des constructeurs ne peuvent pas être dans un enregistrement variant. La règle C++ est en fait plus générique : un type avec un constructeur, un destructeur ou un assignement défini par l'utilisateur ne peut pas être un membre d'une union. Le code suivant illustre un cas qui ne fonctionne pas pour C++ :

type
  TPointD = record
    X: Double;
    Y: Double;
  public
    constructor Create(const X, Y: Double);
  end;

  TRectD = record
    case Integer of
      0:
        (Left, Top, Right, Bottom: Double);
      1:
        (TopLeft, BottomRight: TPointD);
  end;

Le code C++ résultant déclenche des erreurs de compilation :

struct DECLSPEC_DRECORD TRectD
{
  #pragma pack(push,1)
   union
    {
      struct
      {
        TPointD TopLeft;     // Error
        TPointD BottomRight; // Error
      };
      struct
      {
        double Left;
        double Top;
        double Right;
        double Bottom;
      };

    };
  #pragma pack(pop)
};

Utilisation de paramètres chaîne par défaut non vides

Les paramètres chaîne par défaut non vides génèrent l'avertissement suivant :

W8058 Impossible de créer un en-tête précompilé : données initialisées dans l'en-tête

Notez que ce problème affecte uniquement les compilateurs C++ de précédente génération (BCC32 et BCCOSX), elle n'affecte pas les compilateurs C++ améliorés par Clang.

Voir aussi