関数プロトタイプ宣言はヘッダファイルとソースファイルのどちらに書くべきか
C言語のコードでは、全ての関数プロトタイプ宣言がヘッダファイルに書いてあったりするものもありますが、 個人的にはヘッダファイルに書くべきものと、ソースファイルに書くべき1ものを明確に区別すべきだと思います。
何をもって区別するのかと言うと、「スコープ」です。 「何を当たり前のことを」と思った方は申し訳ありません。ただ、最近「スコープなんて知らん」と言わんばかりのコードを大量に読むことになってしまいまして。
スコープとは
ある変数名や関数名といった名前を参照できる範囲のこと
言語によって指定できるスコープには違いがありますが、C言語の関数の場合は public
か private
を区別すべきかと思います2。
C言語に public
や private
という修飾子はないですが、static
や extern
だと誤解を与える可能性があるので、ここではあえて public
、private
を使います。
public
は他のモジュール(ファイル)から参照可能な関数。
private
はそのモジュール(ファイル)内でのみ参照可能な関数です。
例えば、特定のハードウェアを監視しているようなモジュールの場合、外から値をチェックするための関数は public
ですが、
特定の状態変化を検知して記録するような関数は private
になる可能性が高いです3。
スコープは狭く
一般的にスコープは狭くしておいた方が安全です。 ハードの状態変化を外から設定できてしまうと、間違ってその関数を別のモジュールから呼んでしまった場合、重大なバグの原因になりえます。
そんなの間違えて呼ばないよ。と思うかもしれません。 自分だけで書いている場合はそうかもしれないですが4、 チームで開発していると誰がどんな勘違いをするかわかりません。
public 関数はヘッダファイルに、private 関数はソースファイルに書く
ヘッダファイルに書かれてる物は、複数のファイルから参照されることを前提にしています5。
つまり、「ヘッダファイルに書いてある」=public
と言えます。
先の例だと、以下のように状態を見る関数はヘッダファイルに、状態を記録する関数はソースファイルに書きます。
なお、ソースファイルに書く場合、先行して定義出来る場合はプロトタイプを書かなくても良いでしょう。個人的には書かない方が修正時等に余計な手間が減ってよいと思います。
1 2 3 4 5 6 | #ifndef HOGE_H__ #define HOGE_H__ extern int hoge_get_state(); #endif // HOGE_H__ |
1 2 3 4 5 6 | #include "hoge.h" // このプロトタイプ宣言は書かなくても良い場合もある static void set_state( int state); // 関数の実体等々 |
あえて、extern
と static
を書きました。
上記の定義に従うと、public
な関数は extern
で private
な関数は static
になります。
実は、extern
なプロトタイプ宣言をソースファイルの中に書いても良いわけですが、それだと各ソースファイルに同じプロトタイプ宣言が散らばることになります。
こういった宣言は一元管理が原則6なので、ヘッダに書いておくべきです。
C++ の場合は public class と private class を区別
C++ でもベターC として使用する場合は原則同じです。
C++ の class を積極的に活用する場合、
class にはアクセス指定子(public
private
protected
)があるので、そちらで関数のスコープ制御を行います。
この時、class 定義をヘッダファイルに書くか、ソースファイルに書くかで、class のスコープを制御するのが良いと思います7。
変数はどうする?
一般的に「グローバル変数は使わないほうが良い」というのは広く認識されているのだと思います。
「グローバル変数」とは、どこからも参照可能になっている変数、つまり public
な変数です。
さらに言えば、extern
宣言されている変数です。
基本的には「グローバル変数」は使用せず、アクセサを用意する方がより安全だと思います。
とはいえ、「単純に読み書き用のアクセサを用意するのはどうよ?」という考え方も一理あります。 C++ の話ですが、興味深いのでリンクをはっておきます。
参考
でも、ローカルルールには従いましょう
プロジェクトによっては、 他のファイルから呼んではいけないローカルヘッダを作る等8、独自のルールでスコープを実現している場合もあります。 そういった場合は、ローカルルールに従いましょう。 全体でポリシーが統一されていない方が、後々問題になることが多いように思います。
大事なのは、スコープをわかりやすく提示することです。それにより、誤使用によるバグが減りますし、モジュールの使い方が明確になりやすいです。
もちろん、public
にしていても使用に注意が必要な関数があったり、理想通りにはいかないことも多いです。
ただ、そういったことを意識していれば、わかりやすいコメントを入れたり、より具体的な対処が出来るのではないかと思います。
参考書籍
この手のことがどこかに書いてあったはずと思って手持ちの本を探しまわったら、これに書いてありました。
外部に公開する関数、変数のみをヘッダで宣言し、外部に公開する必要のない、その他 の作業用の変数、関数については static 指定子を付けるというのが、C でよく見られるモジュール化の手法で す。
p.72 より
この本では、さらにオブジェクト指向っぽい実装を紹介していて実用的です。 私も 2つ以上のアドレスを持つ I2C モジュールを作成する時等に、この本で紹介されている方法を用いています。
これにも書いてあったかなと思ったのですが、関数のスコープに関してはあまり書かれていませんでした。
しかし、以下の項等は今回の話に通じるところがあります。その他の項目も一読の価値ありです。
- 9.2 変数のスコープを縮める9
- 10.6 既存のインターフェースを簡潔にする
0 件のコメント:
コメントを投稿