2006-06-15
最近では、インライン関数をサポートするC処理系も増えてきたので、インライン関数がどういうものかご存知の方も多いことと思います。インライン関数は元々C++からCにバックポートされたものですので、当然C++にもあります。
インライン関数を使うには、関数宣言にキーワード inline を付けることで行います。この inline はinline指定子と呼ばれます。以下に例を挙げます。
inline void foo(int arg)
{
return arg + 123;
}
int bar(int arg)
{
foo(arg + 1) * 2;
}
上の例におけるfoo関数がインライン関数にあたります。この場合には、bar関数内でfoo関数を呼び出している箇所を、foo関数の定義内容でインライン置換することを示唆しています。具体的には、
int bar(int arg)
{
((arg + 1) + 123) * 2;
}
のようになることを期待しているわけです。マクロとは異なり、有効範囲の概念や、引数の返却値の処理は通常の関数と同じように行われますので、マクロでありがちな引数の副作用によるトラブルなどは起こりません。
インライン関数の定義方法にはもう一つあります。それは、メンバ関数のためのものです。(静的であれ、非静的であれ)メンバ関数をクラスの中で宣言する際に、直接定義も記述すれば、それは暗黙的にインライン関数であることを表しています。例えば、
struct A
{
int foo(int arg) { return arg + 123; }
int bar(int arg);
int hoge(int arg);
};
inline int A::bar(int arg)
{
return arg + 456;
}
int A::hoge(int arg)
{
return -arg;
}
のようなクラスがあった場合、fooメンバ関数とbarメンバ関数はともにインライン関数になりますが、hogeメンバ関数は非インライン関数になります。
インライン関数の定義の仕方はこの程度です。また、使い方は普通の関数と同じです。注意すべきなのは、実際にインライン置換が行われる稼動はコンパイラ任せであり、インライン関数として定義したにもかかわらず、実際には普通の関数と同等のコードが生成される可能性もあるということです。つまり、inline指定子はヒント情報に過ぎないということです。
また、インライン関数は、その性質上、関数の定義をコンパイラに見せなければなりません。したがって、通常関数のように、どこか一箇所で実体を定義すればよいのではなく、翻訳単位ごとに関数定義が必要になります。通常は、インライン関数の定義はヘッダファイルで行うことになります。
なお、古い処理系では、インライン関数の定義は、呼び出しより前に行わなければなりませんでした。すなわち、インライン置換する上で、前方参照ができなかったのです。しかし、標準C++にはそのような制約はありません。ただし、インライン関数の定義を前方参照する場合でも、インライン置換されるかどうかはまた別の話です。少しでも確実にインライン置換させるには、インライン関数の定義は、呼び出しより前に記述する方がよいでしょう。
もう一点、インライン関数の内部で、静的記憶域期間を持つオブジェクトを宣言した場合ですが、インライン関数が内部結合でない限り、すべての翻訳単位で、そのオブジェクトは同一のものを参照することになります。古い処理系では、翻訳単位ごとに別の実体が生成されてしまうこともありましたので、念のため、使用される処理系の振る舞いを確認しておいた方が無難です。
最後に、Cの場合も、C99からインライン関数が標準の機能になりました。しかし、C++のインライン関数より処理系定義や未既定の部分が多く、十分注意する必要があります。
例えば、C++のインライン関数はインライン置換を示唆するものですが、C99のインライン関数は、呼び出しを可能な限り速くすることを示唆するためのものです。つまり、相対アドレスを使ったアドレッシングモードを使うなどの方法でもよいわけです。
さらに、C99の場合、外部結合を持つインライン関数は、内部に、変更可能な静的記憶域間を持つオブジェクトの宣言を含むことができませんし、外部定義とインライン定義の違いに関する難解な仕様もあります。
このように、C++のインライン関数とC99のインライン関数はかなり異なります。ましてや、各C処理系が独自に拡張したインライン関数であれば、さらに仕様が異なることが予想されます。
Comment