LoginSignup

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

C++ では、インクルードガードの機能がなぜコンパイラに実装されないのか

Q&AClosed

インクルードガードの機能がなぜコンパイラに実装されないのか

最近、業務の都合で C++ を使うようになりました。
普段は Python を使っています。
手始めにコーディング規約から整理していましたが、ふと疑問に思うことがありました。

Google C++ スタイルガイド-インクルードガード では、以下のように記載されています。

すべてのヘッダーファイルは、インクルードガード(#define guards)を持たなくてはなりません。
その際のシンボル名は_H_の形でなくてはなりません。
ユニーク性を保証するため、プロジェクト内ソースツリーのフルパスに基づくものにします。 たとえば、プロジェクトfooに含まれるファイルfoo/src/bar/baz.hのガードは次のようにします。

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_

...

#endif  // FOO_BAR_BAZ_H_

ヘッダファイルの循環参照を避けるための記法だということは理解しています。
一方で、例えば、Python であれば、同じモジュールを何度 import してもエラーにはなりません。
ではなぜ、このインクルードガードの機能が C++ のコンパイラには実装されないのでしょうか。
これが実装されることによって生じる問題があるためかと思いますが、初学者には考え付きませんでした。
もし、C++ 言語開発段階でのこの提案に対するリジェクトがあればその理由も知りたいです。

以上です。
ご回答よろしくお願いします。

0

4Answer

現在のコンパイラ技術やエコシステムの事情では好ましくないので C++ のプログラムの分割の仕組みは整理されなおしています。 C++20 (C++ の 2020 年の改定) ではモジュールという概念が導入され、インポート宣言で他のモジュールの機能を取り込むことができるようになりました。 まだ基本機能が入っただけで標準ライブラリがモジュールとして再構成されていないなど実際の活用には時期尚早ですが、より現代的な機能はすでに採用されています。


新しい機能は横に置いて、旧来のシステムの事情を説明します。

C++ の処理は各種の解釈を「フェイズ」と呼ばれる段階に分けて処理することになっており、 #include はかなり序盤に処理されます。 この時点はプログラムの内容をほとんど解釈しておらず、ただ字句をそっくりそのまま埋め込むだけです。

#include は内容を理解しないということを利用してヘッダ以外を別ファイルに分離するという使い方がされる場合もありました。 以下のような事例です。

hoge.csv
1,2,3,4
int main(void) {
	int a[]= {
#include "hoge.csv"
};
}

むしろプログラムの内容に繰り返しがあるときに #include を利用する (複数回おなじファイルを #include する) という活用法も考えられ、勝手にガードされると困ります。

また実際にそのような事例はあるので途中で仕様変更することも出来ませんでした。


こうなっている初期の事情というのは C++ の設計方針として

  • C++ は言語である (開発ツールの詳細を規定したり口出しはしない。 (実際にはリンカで使える識別子が短すぎるのを改善させる活動はしたようです。))
  • C++ は不格好でも今、この瞬間に使える言語でなければならない (ツール類が C++ のために出揃うのを待っていられない。 C++ は現実の道具なので使えない仕様を作っても仕方がない。 現実的な仕様を目指す)

というものがあって C の開発ツールをほとんどそっくりそのまま使えるようにしたからです。 #include は C のルールの踏襲です。

C の仕様が ANSI の仕様として確立したのは 1989 年ですが、それ以前から使われていたのを取りまとめる形での仕様です。 実際には 1970 年代の事情が C には反映されています。 ハードウェアは貧弱で、ソフトウェア技術も未熟、どういう言語仕様が正解なのか全然わからない。 そのときにはそれが正解と思えたからそうしたという以上の詳細な事情はないんじゃないでしょうか。

ちなみに C++ の歴史は 1979 年に "C with Classes" と名付けられた言語から始まっており、 C の後継としてではなくほとんど平行して発展しています。 C++ は C と足並みをそろえるという選択をしたので駄目な部分もある程度は真似ています。

4

Comments

  1. @tiritori

    Questioner
    稚拙な質問にご回答ありがとうございました!
    初学者の私にも理解できる内容でとても助かります。

    記述頂いた事例と、サンプルでおっしゃることがよく分かりました。そのような書かれ方があるのであれば、仕様変更は容易ではないですね、、、

    ご回答ありがとうございました。
    こちらの回答で私の質問はクローズさせていただきます。

わかっているかもしれませんが、Pythonのimport文とC/C++のincludeディレクティブは全く別物ですから、あまり比較するのはよろしくないかと。

そもそも、「同じファイルを2度includeしてはいけない」という話になっていますが、逆に「同じファイルを2度includeさせる」というテクニックもあります。

例えば、列挙体の定義と、それに合わせた名前の文字列を返す関数を作ります。

id.h
typedef enum {
    ID_HOGE,
    ID_PIYO,
    ID_FUGA
}   ID;
id.c
#include "id.h"
const char *id_name(ID id)
{
    const char *names[] = {
    "HOGE",
    "PIYO",
    "FUGA"
    };
    return names[id];
}

上記のようなソースコードだと、定義が複数のファイルに分かれているため、どちらかのファイルだけ直して片方は忘れてしまう、という間違いをしがちです。
それを補うために、以下のようにします。

def_id.h
DEFINE_ID(HOGE)
DEFINE_ID(PIYO)
DEFINE_ID(FUGA)
id.h
typedef enum {
#define DEFINE_ID(n)    ID_##n,
#include "def_id.h"
#undef  DEFINE_ID
}   ID;
id.c
#include "id.h"
const char * id_name(ID id)
{
    const char *names[] = {
#define DEFINE_ID(n)    #n,
#include "def_id.h"
#undef  DEFINE_ID
    };
    return names[id];
}

(最近Cを書いていないので、コンパイルが通らなかったらごめんなさい)

このようにすると、def_id.hを編集するだけで対処できるようになります。

まぁ極端な例かもしれませんが、いずれにせよincludeディレクティブはあくまでソースコードをincludeするだけの機能、includeした結果どのようになるか(二重定義で文法エラーになるか、など)は書いた人の責任というような考えだと思います。
(基本的に過去からそうだから、という理由でしょうが、互換性を無視してまで変えるほどの理由はないというのも理由の一つだと思います)

2

Comments

  1. @tiritori

    Questioner
    ご回答ありがとうございます!
    ご指摘とサンプルコードもよく理解できました。

    頂いたご回答としては、以下2点でしょうか。
    ・書いた人の責任だから
    ・互換性を切り捨てるほどではないから

    たしかに、考えてみればおっしゃる通りだと感じます。ご回答ありがとうございました。

Pythonのimport文はクラスや関数内外のスコープの異なる箇所で用いる言語(インタープリタ?)の機能です。

一方、

#include "xxx.h"
#include <xxx.h>
は当初、コンパイラへの指示する定義体として登場してます。(だってコメントですから)

私の個人的な見解はC言語の時代テンプテートを導入した際、及び、C++言語の時代オーバーライドを導入した際、コンパイラへの指示を複雑にするのではなく、テンプテートとオーバーライドを優先させるため、#includeはファイルに付き1箇所に限定したのでは?と思っていました。

2

Comments

  1. @tiritori

    Questioner

    > コンパイラへの指示する定義体
    こちらは把握できていなかった情報でした。
    ご指摘とコメントありがとうございます。

    頂いたご回答としては、以下のようにまとめられるでしょうか。
    ・C言語からのテンプレート、C++からのオーバーライドの機能の導入を考えているため

    納得できるご回答をありがとうございました。

Comments

  1. @tiritori

    Questioner
    ご回答ありがとうございます。

    質問が分かりにくく申し訳ないです。
    私の質問の意図としては、
    ご指摘のようなテクニックや、一般に使われるコード規約になる手法が、なぜ直接コンパイラに実装されないのか?、です。

    ご回答ありがとうございました。

Your answer might help someone💌