[Contents]   [Back]   [Prev]   [Up]   [Next]   [Forward]  


C++ 言語に対する拡張

GNU コンパイラは、 C++ 言語に対して、 以下に示す拡張機能を提供しています (さらに、 C 言語に対する拡張機能のほとんども、 C++ プログラムの中で使うことができます)。 これらの拡張機能が利用可能であるかどうかをチェックするソース・コードを書きたいのであれば、 C のプログラムの場合と同様の方法によって、 GNU コンパイラであるかどうかをテストすることができます。 それは、 あらかじめ定義されたマクロ __GNUC__ があるかどうかをチェックするという方法です。 特に GNU C++ であるかどうかをテストするには、 __GNUG__ を使うことができます。 (see section `Standard Predefined Macros' in The C Preprocessor

C++ における名前付きの戻り値

GNU C++ では、 関数定義の構文を拡張することによって、 C++ プログラムの関数の戻り値の名前を、 定義本体の外で指定することができます。

type
functionname (args) return resultname;
{
  ...
  body
  ...
}

ある関数がクラス型の戻り値を持つ場合に、 生成関数が余分に呼び出されるのを回避するために、 この機能を使うことができます。 例えば、 `X v = m ();' として宣言される、 クラス X 型の戻り値を持つ関数 m を考えてみましょう。

X
m ()
{
  X b;
  b.a = 23;
  return b;
}

m は引数を1つも取らないように見えますが、 実際には暗黙の引数を1つ取ります。 それは戻り値のアドレスです。 関数の呼び出し時に、 v を保持するのに十分な大きさの領域へのアドレスが、 暗黙の引数として渡されます。 そのあとで b が生成され、 そのフィールド a に 23 という値がセットされます。 最後に、 コピー生成関数 (`X(X&)' という形式の生成関数) が b に対して適用される際に、 (暗黙のうちに渡された) 戻り値の格納位置がその結果を格納する箇所として使われ、 こうして v が戻り値に結合されます。

しかし、 これには無駄があります。 局所変数 b は、 ただちに外部にコピーされてしまうものを保持するためだけの目的で宣言されています。 プロシージャ間(interprocedural)データ・フロー解析に「省略(elision)」アルゴリズムを組み合わせたコンパイラであれば、 おそらくはこのような無駄のすべてを除去することができるでしょう。 しかしそれよりも、 明示的に戻り値を操作することによって、 コンパイラが効率的なバイナリ・コードを生成するのを支援することで、 結果として局所変数やコピー生成関数をすべて回避できるようにする方がはるかに実用的でしょう。

GNU C++ の拡張された関数定義構文を使うことで、 最初に戻り値に r という名前を与え、 そのフィールド a に直接代入することによって、 一時的な割り当てやコピーの作成を回避することができます。

X
m () return r;
{
  r.a = 23;
}

r の宣言は標準的かつ正当な宣言であり、 m の本体のどの部分が実行されるよりも前に、 その宣言が実行されます。

このような種類の関数を使うことで、 追加的な制限が加えられることはありません。 return 文を実行することもできますし、 関数本体の終端に達する (「端から落ちる」) ことによって暗黙のうちに復帰することもできます。

X
m () return r (23);
{
  return;
}

のような場合でも (あるいは、 `X m () return r (23); { }' のような場合ですら)、 その意味は不明瞭ではありません。 というのは、 いずれの場合でも戻り値 r が初期化済みであるからです。 以下のソース・コードは読みにくいかもしれませんが、 これもどのように動くか予測できます。

X
m () return r;
{
  X b;
  return b;
}

最初に r によって表される戻り値の格納箇所が初期化されますが、 `return b;' という文によって、 その格納箇所の値は無効になります。 コンパイラは、 r を破棄して (この際、 消去関数があればそれを呼び出しますが、 消去関数がなければ何も実行しません)、 そのあとで rb で再初期化することによって、 これを処理します。

この拡張機能は主に、 多重定義された(overloaded)演算子を使う人々を補助するために提供されています。 多重定義された演算子を使うと、 関数の引数だけではなく戻り値も制御する必要が大いにあります。 コピー生成関数が性能に対して重大な悪影響をもたらすようなクラスについては (特に、 高速なデフォルトの生成関数が存在する一般的なケースでは)、 これは大きな節約になります。 この拡張機能の不利な点は、 戻り値のデフォルトの生成関数がいつ呼び出されるかを制御できないということです。 それは、 常に最初に呼び出されます。

C++ における最小値演算子と最大値演算子

2つの引数の「最小値」あるいは「最大値」を返してくれる演算子があると非常に便利です。 GNU C++ では、 以下の演算子が利用できます (GNU C では利用できません)。

a <? b
数値 ab のうち小さいほうを返す最小値演算子
a >? b
数値 ab のうち大きいほうを返す最大値演算子

これらの演算子は、 通常の C++ においてはプリミティブ(primitive)ではありません。 というのは、 以下の例に示すように、 C++ では2つの値の最小値を返すマクロを使うことができるからです。

#define MIN(X,Y) ((X) < (Y) ? : (X) : (Y))

このあと `int min = MIN (i, j);' のようにして、 変数 ij の最小値を min にセットします。

しかし、 X もしくは Y に副作用があると、 意図されない動作をすることになるかもしれません。 例えば、 MIN (i++, j++) は、 小さいほうのカウンタ値を2回インクリメントすることになるので、 正常に動作しません。 GNU C の拡張機能によって、 このような問題を回避した安全なマクロを書くことができます (see section 式の型に対する名前付け)。 しかし、 MINMAX をマクロとして書くと、 基本的な算術演算に対して関数呼び出しの表記法を使うことを強制されることにもなります。 GNU C++ の拡張機能を使えば、 `int min = i <? j;' のように書くことができます。

<?>? はコンパイラの内部に組み込まれているので、 副作用を持つ式も正しく処理することができます。 `int min = i++ <? j++;' は正しく動作します。

GNU C++ における goto と消去関数

C++ のプログラムでは、 goto 文を安全に使うことができます。 消去関数を必要とする集合体を包含しているブロックの中から外に出るのに goto 文を使うと、 goto が制御を移す前にその消去関数が実行されます。

しかし、 生成関数を必要とするスコープの内部に入るために goto を使うことは、 コンパイラが許しません。

1つのヘッダ・ファイルにおける宣言と定義

C++ のオブジェクト定義はかなり複雑になることがあります。 原則として、 複数のソース・ファイルにわたって使う個々のオブジェクトについて、 ソース・コードの中で必要なものが2つあります。 第1に、 型宣言と関数プロトタイプによってオブジェクトの構造を記述するインターフェイス仕様が必要です。 第2に、 オブジェクトの実装そのものが必要です。 ヘッダ・ファイル中に分離されたインターフェイスの記述を、 実際の実装の記述に正しく対応するよう保守するのは、 退屈な作業になることがあります。 インターフェイス定義と実装定義が分離されると、 両者は正しく対応しなくなるかもしれないので、 これは危険でもあります。

GNU C++ では、 これら2つの目的を実現するのに1つのヘッダ・ファイルだけで済ませることができます。

警告: これを指定するメカニズムは、 現在変更される過渡期にあります。 当面は、 以下に説明する2つの #pragma コマンドのどちらかを使わなければなりません。 将来の GNU C++ のリリースでは、 これに代わるメカニズムによって、 これらの #pragma コマンドは不必要になるでしょう。

まず、ヘッダ・ファイルの中に完全なオブジェクト定義を書きます。 さらに、 そのヘッダ・ファイルのソース・コードに `#pragma interface' という印を付けます。 この印によって、 コンパイラは、 普通のソース・ファイルが #include によってそのヘッダ・ファイルを組み込んだ場合には、 そのヘッダ・ファイルをインターフェイス仕様としてのみ使うことができるようになります。 完全な実装定義が組み込まれるべきただ1つのソース・ファイルの中では、 そのヘッダ・ファイルの別の用途を指示するために、 名前付け規約もしくは `#pragma implementation' のどちらかを使うことができます。

#pragma interface
#pragma interface "subdir/objects.h"
オブジェクトのクラスを使うオブジェクト・ファイルのほとんどで領域を節約するには、 それらのオブジェクトのクラスを定義するヘッダ・ファイルの中でこの指示子を使ってください。 通常は、 クラス定義を組み込む個々のオブジェクト・ファイルの中に、 特定の情報 (インライン・メンバ関数、 デバッグ情報、 仮想関数を実装する内部テーブルなどの予備的なコピー) の局所的なコピーが保持されなければなりません。 このような情報の複製を回避するために、 このプラグマを使うことができます。 コンパイル時に `#pragma interface' を含むヘッダ・ファイルが組み込まれると、 (そのヘッダ・ファイルを組み込んだ入力ソース・ファイル自体が `#pragma implementation' を使っていなければ) こうした予備的な情報は生成されません。 その代わりに、 オブジェクト・ファイルの中には、 リンク時に解決されることになる参照が含まれることになります。 この指示子の2番目の形式は、 異なるディレクトリに同じ名前のヘッダ・ファイルが複数存在するような場合に役に立ちます。 この形式を使う場合には、 `#pragma implementation' に対しても同一の文字列を指定しなければなりません。
#pragma implementation
#pragma implementation "objects.h"
組み込まれるヘッダ・ファイルから完全なオブジェクト定義を生成させたい (そしてそれを広域的に可視にしたい) 場合に、 ヘッダ・ファイルを組み込む入力ファイルの中でこのプラグマを使ってください。 これに対して、 組み込まれるヘッダ・ファイルでは `#pragma interface' を使います。 インライン・メンバ関数、 デバッグ情報、 仮想関数を実装するために使われる内部テーブルなどの予備的なコピーは、 すべて実装ファイルの中に生成されます。 `#pragma implementation' を引数を指定せずに使うと、 それはソース・ファイルと同一のベース名 (11) を持つインクルード・ファイルに対して適用されます。 例えば、 `allclass.cc' の中に単に `#pragma implementation' とだけ書くのは、 `#pragma implementation "allclass.h"' と書くのと同じことです。 2.6.0 よりも前のバージョンの GNU C++ では、 たとえ `#pragma implementation' を指定していなくても、 `allclass.cc' から組み込まれた時にはいつでも、 `allclass.h' は実装ファイルとして取り扱われていました。 これは良いところよりも悪いところの方が多いとみなされ、 廃止されました。 明示的に `#pragma implementation' を使うのであれば、 ソース・ファイル中においてこのプラグマにより影響を受けるヘッダ・ファイルを組み込むところよりも前にそれを書かなければなりません。 複数のヘッダ・ファイルの中のソース・コードを単一の実装ファイルに組み込ませたいのであれば、 文字列引数を使ってください (ヘッダ・ファイルを組み込むには `#include' を使わなければなりません。 `#pragma implementation' は、 そのヘッダ・ファイルの使い方を指定するだけであり、 実際にそれを組み込む訳ではありません)。 1つのヘッダ・ファイルの内容を分割して、 複数の実装ファイルの中へ組み込む方法はありません。

`#pragma implementation'`#pragma interface' は関数のインライン展開にも影響を及ぼします。

`#pragma interface' の指定されたヘッダ・ファイルの中でクラスを定義すると、 そのクラスの中で定義されている関数に対して、 明示的な extern 宣言と類似した影響が及ぼされます。 この場合、 コンパイラが、 その関数を独立したものとして定義するためにバイナリ・コードを出力することはありません。 その関数の定義は、 呼び出し側においてインライン展開するためだけに使われます。

これとは逆に、 同じヘッダ・ファイルに対して `#pragma implementation' を宣言するソース・ファイルの中でこのヘッダ・ファイルを組み込むと、 コンパイラは、 その関数自体のバイナリ・コードを出力します。 これによって、 ポインタを経由して (あるいは、 インライン展開を使わずにコンパイルされた呼び出し側から) アクセス可能な関数が定義されます。 その関数に対するすべての呼び出しをインライン展開することができるのであれば、 `-fno-implement-inlines' を指定してコンパイルすることによって、 関数自体のバイナリ・コードを出力することを回避することができます。 ただし、 インライン展開されない呼び出しが1つでもあれば、 リンクする際にエラーが発生することになります。

テンプレート・インスタンスの存在箇所

C++ テンプレートは、 通常 UNIX システム上において環境が提供してくれる情報よりもさらに多くの情報を環境が提供してくれることを必要とする最初の言語機能です。 コンパイラとリンカは、 何らかの方法によって、 個々のテンプレートのインスタンスが必要である場合には、 そのインスタンスが実行ファイルの中において確かにただ一度だけ生成され、 それが必要でない場合には、 そのインスタンスは確かに1度も生成されないようにしなければなりません。 この問題には、 以下において Borland モデルおよび Cfront モデルとして言及する2つの基本的な解決方法があります。

Borland モデル
Borland C++ は、 共通ブロックに相当するバイナリ・コードをリンカに対して渡すことによって、 このテンプレートのインスタンス生成の問題を解決しました。 テンプレートのインスタンスを使う個々の翻訳単位の中において、 コンパイラがテンプレートのインスタンス生成コードを出力します。 重複するインスタンス生成コードはリンカによって破棄され、 1つだけが残されます。 このモデルの利点は、 リンカがオブジェクト・ファイル自体のことだけを考慮すれば良いということです。 オブジェクト・ファイルの外部には、 考慮しなければならない複雑な問題はありません。 このモデルの不利な点は、 テンプレートのソース・コードが繰り返しコンパイルされるため、 コンパイル時間が増加するということです。 このモデルに合わせて書かれたソース・コードでは、 すべてのテンプレートの定義がヘッダ・ファイルの中に含まれる傾向があります。 というのは、 テンプレートのインスタンスを生成するためには、 その定義を参照しなければならないからです。
Cfront model
AT&T の C++ トランスレータである Cfront は、 テンプレート・リポジトリという概念を作り出すことによって、 テンプレートのインスタンス生成の問題を解決しました。 テンプレート・リポジトリとは、 自動的に保守されるある領域のことで、 そこにテンプレートのインスタンスが格納されます。 より現代的なバージョンのリポジトリは以下のように機能します。 コンパイラは、 個々のオブジェクト・ファイルを作る際に、 その中にある1つ1つのテンプレートの定義とインスタンス生成コードをリポジトリの中に置きます。 リンク時に、 リンク・ラッパがオブジェクトをリポジトリの中に加え、 必要なテンプレートのインスタンスのうち、 その時点までに生成コードが出力されていなかったものをコンパイルします。 このモデルの利点は、 コンパイル速度がより最適であることと、 システム上のリンカをそのまま使用できることです。 Borland モデルを実装するためには、 コンパイラの供給元が、 システム上のリンカを独自のリンカで置き換える必要があります。 Cfront モデルの不利な点は、 複雑さの度合いが非常に大きくなり、 したがってエラーの可能性もまた非常に大きくなるということです。 ソース・コードによっては、 このモデルによって何の影響も被らないということがあるかもしれませんが、 実際上は、 1つのディレクトリを使って複数のプログラムを構築したり、 複数のディレクトリを使って1つのプログラムを構築したりすることは非常に困難になる可能性があります。 このモデルに合わせて書かれたソース・コードでは、 インライン展開されないメンバ・テンプレートの定義を別のファイルに分ける傾向があります。 このファイルは別にコンパイルしなければならなくなります。

Linux/GNU や Solaris 2 のような ELF システム、 もしくは、 Microsoft Windows 上において、 バージョン 2.8 以降の GNU ld と一緒に使っている場合には、 g++ は Borland モデルをサポートしています。 他のシステム上では、 g++ はどちらの自動モデルも実装していません。

将来のバージョンの g++ では、 混成モデルをサポートする予定です。 この混成モデルでは、 コンパイル時にテンプレート定義が組み込まれているものについては、 コンパイラがインスタンス生成コードを出力し、 それ以外のものについては、 コンパイラがテンプレート定義とインスタンスのコンテキスト情報をオブジェクト・ファイルの中に格納します。 リンク・ラッパは、 残りのインスタンスを生成するために、 この情報を必要に応じて取り出してコンパイラを起動します。 その後に、 リンカが重複するインスタンス生成コードを破棄して、 1つだけを残します。

混成モデルがサポートされるまでの間は、 テンプレートのインスタンス生成を処理するための選択肢として、 以下のような方法があります。

  1. 暗黙のうちにテンプレートのインスタンスが生成されないようにするために `-fno-implicit-templates' を指定してソース・コードをコンパイルし、 実際に使用するすべてのインスタンスを明示的に生成します。 この方法では、 他の方法と比較して、 正確にはどのインスタンスが必要であるかという点に関してより多くの知識が要求されますが、 謎めいた部分はより少なく、 より細かく制御することができます。 プログラムの全体にわたって、 明示的なインスタンス生成を散在させることができます。 おそらくインスタンスの生成は、 インスタンスが使われる翻訳単位の中や、 テンプレートそのものが定義されている翻訳単位の中で行われるでしょう。 必要となる明示的なインスタンス生成のすべてを1つの大きなファイルの中に置くこともできますし、 必要となるインスタンスの1つ1つについて以下のような小さなファイルを作り、 それらを集めてテンプレート生成を行うライブラリを作成することもできます。
    #include "Foo.h"
    #include "Foo.cc"
    
    template class Foo<int>;
    template ostream& operator <<
                    (ostream&, const Foo<int>&);
    
    Cfront モデルに従うソース・コードを使っていれば、 `#include' によってメンバ・テンプレートの定義を組み込むことのないファイルをコンパイルする際には、 おそらく `-fno-implicit-templates' を使わずに済ませることができるでしょう。 インスタンス生成を行うのに1つの大きなファイルを使うのであれば、 明示的なインスタンス生成により要求した (しかし他のファイルからは要求されない) すべてのインスタンスを、 そもそも明示的な指定などしなくても生成することができるようにするために、 その1つのファイルを `-fno-implicit-templates' を指定せずにコンパイルすると良いかもしれません。 明示的なインスタンス生成に対する前方宣言、 テンプレート・クラスのメンバの明示的なインスタンス生成、 また、 テンプレート・クラスのメンバを1つも生成することなく、 そのテンプレート・クラスに対するコンパイラ・サポート・データ (すなわち仮想テーブル) を生成することを可能にするために、 g++ では Working Paper に概説されているテンプレートのインスタンス生成構文を拡張しています。
    extern template int max (int, int);
    template void Foo<int>::f ();
    inline template class Foo<int>;
    
  2. g++ が自動的なインスタンス生成管理を実装していると想定して、 何も行わないことにします。 Borland モデルに合わせて書かれたソース・コードは問題なく動きますが、 個々の翻訳単位の中に、 そこで使われる個々のテンプレートのインスタンスがすべて含まれることになります。 大きなプログラムでは、 これによって、 許容できない量のバイナリ・コードの重複が発生する可能性があります。
  3. テンプレート定義を持つすべてのファイルに `#pragma interface' を書き加えます。 また、 これらのファイル1つ1つについて、 それを `#include' によって組み込む `.C' ファイルの先頭に `#pragma implementation "filename"' を書き加えます。 その後に、 すべてのファイルを `-fexternal-templates' を指定してコンパイルします。 するとテンプレートは、 それを実装する (すなわち、 そのテンプレート定義が存在するファイルに対して `#pragma implementation' を指定する行を含む) 翻訳単位においてのみ展開されます。 他のファイルはすべて外部参照を使います。 運が良ければ、 何もかもうまく行くでしょう。 未定義シンボルのエラーが出るようであれば、 プログラムの中で使われるテンプレートのインスタンスはいずれも、 そのテンプレートを実装するファイルの中で使われていることを確かめる必要があります。 ある特定のインスタンスについてはそのファイルの中でまったく用途がないのであれば、 最新の C++ Working Paper にある構文を使って、 明示的にインスタンスを生成することができます。
    template class A<int>;
    template ostream& operator << (ostream&, const A<int>&);
    
    この方法は、 どちらのモデルに合わせて書かれたソース・コードでもうまく機能します。 Cfront モデルに合わせて書かれたコードを使っているのであれば、 クラス・テンプレートを含むファイルとそのメンバ・テンプレートを含むファイルとは、 同一の翻訳単位の中に実装されていなければなりません。 これとわずかに異なる解決方法に、 フラグ `-falt-external-templates' を使うという方法があります。 このフラグによって、 テンプレートのインスタンス生成コードは、 テンプレートが定義されるファイルを実装する翻訳単位の中ではなく、 テンプレートのインスタンスがその中で最初に生成されるヘッダ・ファイルを実装する翻訳単位の中に出力されるようになります。 このヘッダ・ファイルは、 すべての翻訳単位において同一でなければなりません。 さもないと、 問題が発生するでしょう。 これらのプラグマに関するより詳しい議論については、See section 1つのヘッダ・ファイルにおける宣言と定義

シグニチャを使った型の抽象化

GNU C++ では、 キーワード signature を使って、 完全な抽象クラスのインターフェイスをデータ型として定義することができます。 シグニチャ・ポインタを使うことによって、 この抽象化された型を実際のクラスに関係付けることができます。 シグニチャを使いたいのであれば、 `-fhandle-signatures' コマンドライン・オプションを指定して GNU コンパイラを実行してください (このオプションを指定すると、 コンパイラは将来の拡張のために sigof も予約語として扱います)。

概略的には、 シグニチャとは、 クラスの型の抽象化もしくはインターフェイスです。 他の言語にも同様の便利な機能を持つものがあります。 C++ のシグニチャは、 ML のシグニチャ(signature)、 Haskell の型クラス(type class)、 Modula-2 の定義モジュール(definition module)、 Modula-3 のインターフェイス・モジュール(interface module)、 Emerald の抽象型(abstract type)、 Trellis/Owl の型モジュール(type modules)、 Scratchpad II のカテゴリ(category)、 POOL-I の型(type)と関係があります。 シグニチャに関するより詳細な議論については、 GeraldBaumgartner と Vincent F. Russo による Signatures: A Language Extension for Improving Type Abstraction and Subtype Polymorphism in C++ (Tech report CSD--TR--95--051, Dept. of Computer Sciences, Purdue University, August 1995、 若干改善されたバージョンが Software--Practice & Experience, 25(8), pp. 863--889, August 1995 に収められています) を参照してください。 この Tech report は anonymous FTP によって ftp.cs.purdue.edu から `pub/gb/Signature-design.ps.gz' というファイル名で入手可能です。

構文的には、 シグニチャの宣言は、 メンバ関数宣言や入れ子になった型宣言の集合です。 例えば、 以下のシグニチャ宣言は、 メンバ関数 `int foo ()'`int bar (int)' を持つ新しい抽象型 S を定義しています。

signature S
{
  int foo ();
  int bar (int);
};

シグニチャ型は実装の定義を含まないので、 シグニチャのインスタンスを直接書くことはできません。 その代わりに、 必要なインターフェイスを持つ任意のクラスへのポインタをシグニチャ・ポインタとして定義することができます。 このようなクラスは、 そのシグニチャ型の実装になります。

あるクラスを S の実装として使うためには、 そのクラスが public なメンバ関数 `int foo ()'`int bar (int)' を持っていることが確実でなければなりません。 そのクラスは、 public であるか否かにかかわらず、 他のメンバ関数を持つことができます。 シグニチャに宣言されているメンバ関数を提供する限り、 そのシグニチャ型の実装としては適切なクラスです。

例えば、 C がシグニチャ S の要件を満足するクラスである (CS規格合致する) としましょう。 すると、

C obj;
S * p = &obj;

は、 シグニチャ・ポインタ p を定義して、 型 C のあるオブジェクトを指すように初期化します。 メンバ関数呼び出し `int i = p->foo ();' を行うと、 `obj.foo ()' が実行されます。

標準 C++ においては、 抽象仮想クラスがこれといくらかよく似た便利な機能を提供しています。 抽象仮想クラスの代わりにシグニチャを使うことには、 主に2つの利点があります。

  1. 下位の型への変換が、 継承とは独立したものになります。 あるシグニチャ型 S において宣言されているすべてのメンバ関数が、 別のクラス型もしくはシグニチャ型の T にもある限り、 継承階層がどのようなものであるかに関係なく、 型 T は型 S の下位の型になります。 したがって、 クラスの継承階層を反映した型を使うことを強制されるのではなく、 あらゆる継承 (実装) 階層から完全に独立した型の階層を定義することができます。
  2. シグニチャを使うと、 あるシグニチャの実装として既存のクラス階層を扱うことができます。 抽象仮想クラスを使っていると、 既存のクラス階層がコンパイル済みの形式でしか利用できない状況において運の尽きとなります。 抽象仮想クラスを既存のクラス階層上で更新することができないからです。 したがって、 抽象仮想クラスの下位の型としてインターフェイス・クラスを書くことが必要になるでしょう。

シグニチャにはもう1つ些細な点があります。 シグニチャの宣言には、 メンバ関数の宣言のほかに、 メンバ関数の定義を含めることができます。 シグニチャのメンバ関数で完全な定義を持つものは、 デフォルトの実装と呼ばれます。 クラスは、 このシグニチャの規格に合致するために、 この特定のインターフェイスを持つ必要はありません。 例えば、 あるクラス C は、 メンバ関数 `int f0 ()' を実装していても、 実装していなくても、 以下のシグニチャの規格に合致することができます。

signature T
{
  int f (int);
  int f0 () { return f (0); };
};

C::f0 を定義すれば、 その定義が優先されます。 C::f0 が定義されない場合は、 デフォルトの実装である S::f0 が適用されます。


[Contents]   [Back]   [Prev]   [Up]   [Next]   [Forward]