C++ 向きの Delphi コードの記述
Delphi 言語ガイド:インデックス への移動
C++ で Delphi コードを使用することができます。Delphi コマンドライン コンパイラでは、次のスイッチを使用して、C++ で Delphi コードを処理するのに必要なファイルを生成します。
-JL
スイッチを指定すると、.dpk ファイルから .lib ファイル、.bpi ファイル、.bpl ファイル、.obj ファイルが生成されるほか、パッケージ内のすべてのユニットのヘッダー ファイルが生成されます。-JPHNE
スイッチを指定すると、.pas
ユニットから上記と同じことが行われます。
ただし、すべての Delphi 機能が C++ に対応しているわけではありません。このトピックでは、C++ から使用する Delphi 実行時コードの推奨事項と禁止事項を一覧します。
目次
推奨事項
継承するすべてのコンストラクタの再宣言
Delphi とは異なり、C++ ではコンストラクタを継承しません。たとえば、次のコードは誤りです。
class A
{
public:
A(int x) {}
};
class B: public A
{
};
int main(void)
{
B *b = new B(5); // Error
delete b;
}
Delphi コンパイラのヘッダー ファイル生成ロジックでは、この言語上の違いを認識しており、欠けている継承コンストラクタを各派生クラスに追加します。ただし、これらのコンストラクタでは、クラスのメンバ変数の初期化も行います。これらのメンバ変数の 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);
};
- 名前付きコンストラクタ イディオムを使用する。この手法では、コンストラクタのオーバーロード時に同一または類似のパラメータが使用される場合、名前付きコンストラクタではなく、クラスの静的なファクトリ メンバを宣言します。これは Delphi レコード型の場合に特に関係があります。次の例は、この手法に基づいた解決策を表しています。
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)); }
禁止事項
インデックス プロパティのオーバーロード
Delphi では、次のように、インデックス プロパティのオーバーロードが可能です。
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++ では機能しません。クラスの各プロパティは一意でなければならないからです。
コンストラクタからの仮想メソッドの呼び出し
これは、継承するすべてのコンストラクタの再宣言に関係しています。Delphi 形式のクラスの場合、最派生クラスの vtable は基底コンストラクタの呼び出し時に設定されます。これにより、コンストラクタから仮想メカニズムが機能できます。ただし、これは C++ 環境では、クラスのコンストラクタが実行される前にクラスの仮想メソッドが呼び出されたり、基底コンストラクタから実行されたメンバの初期化がクラスのコンストラクタで取り消されるなど、奇妙な動作が発生することを意味します。
エイリアスでのジェネリックスの使用
C++ では、インスタンス化されたテンプレート型に対する Delphi エイリアスを使用できます。ただし、C++ では、依存型を伴う Delphi エイリアスを使用できません。このことを次のコードで説明します。
type
GoodArray = TArray<Integer>;
BadArray<T> = array of T;
GoodArray
は、C++ で使用できる具象型です。これに対して、BadArray
には依存型が含まれているので、C++ では使用できません。
クロージャでのジェネリックスの使用
公開されているイベントの RTTI が生成されることにより、IDE でイベント ハンドラを生成することができます。C++ のイベント ハンドラが生成される場合、ジェネリックスについて生成された RTTI は IDE のロジックでは処理できません。したがって、クロージャではジェネリックスを使用しないようにすることをお勧めします。
コンストラクタを持つレコードの使用
Delphi におけるバリアント レコードは 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 ヘッダに初期化データが含まれているためプリコンパイルヘッダーを作成できない
この問題は、旧世代 C++ コンパイラ(BCC32 と BCCOSX)にのみ影響し、Clang 拡張 C++ コンパイラには影響しない点に注意してください。