C++ 向きの Object Pascal コードの記述

提供: Appmethod Topics
移動先: 案内検索

Object Pascal 言語ガイド:インデックス への移動


C++ で Object Pascal コードを使用することができます。Object Pascal コマンドライン コンパイラでは、次のスイッチを使用して、C++ で Object Pascal コードを処理するのに必要なファイルを生成します。

  • -JL スイッチを指定すると、.dpk ファイルから .lib ファイル、.bpi ファイル、.bpl ファイル、.obj ファイルが生成されるほか、パッケージ内のすべてのユニットのヘッダー ファイルが生成されます。
  • -JPHNE スイッチを指定すると、.pas ユニットから上記と同じことが行われます。

ただし、すべての Object Pascal 機能が C++ に対応しているわけではありません。このトピックでは、C++ から使用する Object Pascal 実行時コードの推奨事項と禁止事項を一覧します。

推奨事項

継承するすべてのコンストラクタの再宣言

Object Pascal とは異なり、C++ ではコンストラクタを継承しません。たとえば、次のコードは誤りです。

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

class B: public A
{
};

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

Object Pascal コンパイラのヘッダー ファイル生成ロジックでは、この言語上の違いを認識しており、欠けている継承コンストラクタを各派生クラスに追加します。ただし、これらのコンストラクタでは、クラスのメンバ変数の初期化も行います。これらのメンバ変数の 1 つをデフォルト以外の値に既に初期化した仮想メソッドが基底クラスで呼び出される場合は、これが問題の原因となります。クラスの delphireturn タイプのメンバが基底コンストラクタで初期化される可能性がある場合は、継承したコンストラクタを再宣言することが特に重要です。

クラス階層における各コンストラクタのシグネチャの個別化

C++ では、名前付きコンストラクタをサポートしていません。そのため、オーバーロードしたコンストラクタでは同一のパラメータも類似のパラメータも使用してはいけません。たとえば、次のコードは C++ で使用する場合には機能しません。

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

上記の例では、次のような C++ コードになりますが、この場合、コンストラクタの重複に関連するコンパイル エラーが発生します。

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

この問題を回避する方法はいくつかあります。

  • デフォルト値を持つダミー パラメータをコンストラクタの 1 つに追加する。ヘッダー ファイル生成ロジックでは、コンストラクタでのデフォルト値をわざと省きます。そのため、2 つのコンストラクタは 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);
};
  • 名前付きコンストラクタ イディオムを使用する。この手法では、コンストラクタのオーバーロード時に同一または類似のパラメータが使用される場合、名前付きコンストラクタではなく、クラスの静的なファクトリ メンバを宣言します。これは Object Pascal レコード型の場合に特に関係があります。次の例は、この手法に基づいた解決策を表しています。
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)); }

禁止事項

インデックス プロパティのオーバーロード

Object Pascal では、次のように、インデックス プロパティのオーバーロードが可能です。

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;

ただし、ヘッダー ファイルに生成されるインターフェイスは C++ では機能しません。クラスの各プロパティは一意でなければならないからです。

コンストラクタからの仮想メソッドの呼び出し

これは、継承するすべてのコンストラクタの再宣言に関係しています。Object Pascal 形式のクラスの場合、最派生クラスの vtable は基底コンストラクタの呼び出し時に設定されます。これにより、コンストラクタから仮想メカニズムが機能できます。ただし、これは C++ 環境では、クラスのコンストラクタが実行される前にクラスの仮想メソッドが呼び出されたり、基底コンストラクタから実行されたメンバの初期化がクラスのコンストラクタで取り消されるなど、奇妙な動作が発生することを意味します。

エイリアスでのジェネリックスの使用

C++ では、インスタンス化されたテンプレート型に対する Object Pascal エイリアスを使用できます。ただし、C++ では、依存型を伴う Object Pascal エイリアスを使用できません。このことを次のコードで説明します。

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

GoodArray は、C++ で使用できる具象型です。これに対して、BadArray には依存型が含まれているので、C++ では使用できません。

クロージャでのジェネリックスの使用

公開されているイベントの RTTI が生成されることにより、IDE でイベント ハンドラを生成することができます。C++ のイベント ハンドラが生成される場合、ジェネリックスについて生成された RTTI は IDE のロジックでは処理できません。したがって、クロージャではジェネリックスを使用しないようにすることをお勧めします。

コンストラクタを持つレコードの使用

Object Pascal におけるバリアント レコードは C++ の共用体と同等です。コンストラクタを持つレコードはバリアント レコードに含めることはできません。C++ のルールは実はもっと一般的です。つまり、ユーザー定義のコンストラクタ、デストラクタ、代入演算子のいずれかを持つ型は、共用体のメンバにはなれません。次のコードは、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;

生成される下記の C++ コードをコンパイルすると、エラーが発生します。

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

空でないデフォルトの文字列パラメータの使用

空でないデフォルトの文字列パラメータを使用すると、次の警告が発生します。

W8058 ヘッダに初期化データが含まれているためプリコンパイルヘッダーを作成できない

なお、この問題は BCC32.EXEBCCOSX.EXE の両コンパイラにのみ影響します。これらの 32 ビット Windows および Mac OS X 向けコンパイラが Clang ベースの C++ コンパイラに移行したら、この制限事項はなくなります。

関連項目