C の場合、タグ名だけでは型名になれず、struct, union, enumを付けなければなりません。そのため、使い勝手を向上するために typedef 名を付けることが多いのではないでしょうか? 一方、C++ではクラスや列挙体のタグ名だけで型名になりますので、そうしたtypedef名はあまり使う機会がないかもしれません。
というわけで、C言語では次のような型定義がよく行われます。
0 1 2 3 4 |
typedef struct _FOO { ... } FOO; |
ところが、このコードの動作は未定義だということをご存知でしょうか? _FOOのように、下線(アンダースコア、アンダーバー)で始まり、下線または大文字が続く識別子は「予約済み識別子」です。予約済み識別子というのは、規格がライブラリに使うか、処理系が作業用または拡張用に使うために予約されている識別子で、それらをユーザープログラムで使用することはできません(使用した場合の動作は未定義です)。
未定義の動作ですから、実行時におかしな振る舞いをするかもしれませんし、コンパイル時にエラーになったり、コンパイラがフリーズしたりするかもしれません。そうはいっても、現実には問題ないだろうと思われるかもしれません。確かに、実際に問題に直面する機会は少ないのですが、多くの場合、この問題に直面するのは異なる環境(同じコンパイラの将来のバージョンを含む)に移植する際なのです。
仮に問題に直面しないとしても、わざわざ未定義の動作を引き起こすような記述をしなければならない理由は一切ありません。ゴキブリを 1 匹発見すると100匹はいるといわれますが、ソースコードの中にこのような記述が見つかると、他の箇所にも多数の怪しい箇所が存在すると考えた方がよいでしょう。
いずれにせよ、こうしたコードは百害あって一利無しです。多くの場合、typedef 名を付けるのであればタグ名は不要です。つまり、
0 1 2 3 4 |
typedef struct { ... } FOO; |
上のように記述すれば済むはずです。線形リストなど、自己参照構造体の場合にはタグ名が不可欠ですが、その場合は、
0 1 2 3 4 |
typedef struct FOO { ... } FOO; |
とでもしておけば十分です。タグ名と型名では名前空間が異なりますから、同じ名前を付けても衝突することがありません。C++ ではタグ名と型名の名前空間は分かれていませんが、C言語との互換性のために、上のようなtypedef名を定義しても問題ないことになっています。ですから、C++との互換性の面でも問題ありません。
予約済み識別子は、タグ名だけでなく、いろいろなところでうっかり使ってしまいがちです。何が予約済み識別子であるかのルールは結構煩雑ですので、下線で始まる識別子は一律使わないことに決めてしまった方が無難です。