C++のファイル分割について、重複定義の意味と #ifndef
の使い方がよく分かりません。
// main.h int funcA(int x); int funcB(int x);
// main.cc #include <iostream> #include "main.h" using namespace std; int main() { cout << funcA(2) + funcB(2) << endl; return 0; }
// funcA.cc int funcA(int x) { return x + 2; }
// funcB.cc #include "main.h" int funcB(int x) { return funcA(2) + 2; }
2つのファイルがmain.hを読み込んでいます。
具体的にどういった問題が発生するのかと、その問題に対する改善をお願いします。
古いバージョンとの互換性は気にしません。
G++ Apple LLVM version 7.3.0 (clang-703.0.31)
cmake_minimum_required(VERSION 3.6)
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2016/08/21 23:53
回答4件
0
この場合は、特に問題は起きません(複数のソースファイルから同じヘッダを呼んでも特段の問題はありません)。
ただ、複数のヘッダから同じヘッダを呼び出すと問題となってきます。
C++
1/********** hoge_class.h ***********/ 2 3class HogeClass{ 4 // 中身は省略 5} 6 7/********** foo_lib.h.h ***********/ 8#include "hoge_class.h" 9 10// 後略 11 12/********** bar_lib.h.h ***********/ 13#include "hoge_class.h" 14 15// 後略 16 17/********** ソースファイル ***********/ 18#include "foo_lib.h" 19#include "bar_lib.h" 20 21int main(){ 22 //中身は省略 23}
このように書いた場合、foo_lib.h
とbar_lib.h
の両方を経由してhoge_class.h
が2回読みこまれてしまい、クラスが二重定義となってエラーになります。
このように、「同じヘッダが2回展開される」ことによるトラブルを防ぐのがインクルードガードです。
C++
1#ifndef HOGE_CLASS_DEFINED 2#define HOGE_CLASS_DEFINED 3 4class HogeClass{ 5 // 中身は省略 6} 7 8#endif
このようにしておくことで、2回めにhoge_class.h
が読み込まれた場合にはすでにHOGE_CLASS_DEFINED
が存在するため、クラス定義自体が無視されるようになります。
なお、現代のほぼすべてのコンパイラが#pragma once
と書けば同じヘッダの読み出しを1回だけにしてくれるので、ややこしければそれを使うのもありです。
投稿2016/08/22 00:22
総合スコア145183
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
こんにちは。
提示されたソースならば、多重定義の問題は置きないと思います。
しかし、聞きたいことはなんとなく分かります。
恐らく、実体の多重定義の話とインクルード・ガード(#ifndef)の話と思います。この2つは別物です。
まず、コンパイル単位という言葉があります。
簡単に説明すると、コンパイラにソース・ファイルを指定してコンパイルしますが、この時の1つのソース・ファイルが1つのコンパイル単位です。他のソースファイルとは全く独立にコンパイルされると言う意味です。
コンパイルされたプログラムはオブジェクトファイル(.obj等)となり、複数のオブジェクトファイルをリンクして1つの実行形式ファイル(.exe等)が作られます。
異なるコンパイル単位に同じ関数の実体が含まれていると多重定義となり、リンカにてエラーになります。
これが実体の多重定義です。
ヘッダで実体を定義してよく発生させてしまいます。例えば下記です。
main.h
C++
1int funcA(int x) { return x + 2; } 2int funcB(int x) { return funcA(2) + 2; }
main.hは、mainA.ccとfuncB.ccの両方から#includeされているので、funcA()とfuncB()の実体がmain.ccコンパイル単位とfuncB.ccコンパイル単位の両方で実体が定義されてしまい、実体の多重定義となります。
1つのコンパイル単位内で、同じものを定義しても多重定義となる場合があります。(ならないように見える場合もあるのでややこしいのですが、#defineで同じシンボルを定義すると確実に多重定義になります。)
この現象が良く発生するのは、#includeでヘッダをインクルードする場合です。
例えば、main.h内で#include <stdio.h>はよくやると思います。そして、#include "main.h"しているcppファイルでも#include <stdio.h>をやらざる得ないケースは、複雑なプロジェクトの場合よくあります。
実際にやっても問題はでません。それは、インクルード・ガードというテクニックが使われているからです。
main.h
C++
1#ifndef MY_PROJECT_MAIN_H 2#define MY_PROJECT_MAIN_H 3 4#define FOO "foo" 5 6#endif
のような使い方をします。
1つのコンパイル単位内でmain.hが初めて#includeされた時はまだMY_PROJECT_MAIN_Hが定義されていませんので、FOOが定義されます。
main.hが次に#includeされた時は、前回のmain.hの#includeにより、既にMY_PROJECT_MAIN_Hが定義されていますから、#ifndefで中身が処理対象になりませんので、FOOの多重定義にはなりません。
投稿2016/08/22 00:49
総合スコア23272
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
ベストアンサー
「重複定義」とは1つのソースコードにて同じ関数が複数箇所で定義されていることを言います。
たしか、redefinition of XXXXとかでたと思います。
この問題は以下を満たすときに起こります。
・あるヘッダファイル(a.h)に、関数が定義(!=宣言)されている
・別のヘッダファイル(b.h)にて、a.hがincludeされている
・問題のソースファイル(main.cc)にて、a.h, b.hが両方includeされている
// a.h int funcA(int x) {return x+1;} // b.h #include "a.h" int funcB(int x) {return x-1;} // main.cc #include "a.h" #include "b.h" int main(int argc, char* argv[]) { printf("hello"); return 0; }
main.ccのコンパイルにおいてincludeが展開された時、a.hで定義されている関数が2つ現れてしまいます。
// include展開後のmain.cc // #include "a.h" -> int funcA(int x) {return x+1;} // #include "b.h" -> int funcA(int x) {return x+1;} int funcB(int x) {return x-1;} int main(int argc, char* argv[]) { printf("hello"); return 0; }
これを防ぐために、ヘッダファイルに「2回、展開されることを防ぐおまじない」をします。
このおまじないが#ifndef
で始まる3行セットです。
// a.h #ifndef A_H_ #define A_H_ int funcA(int x) {return x+1;} #endif //A_H_ // b.h #ifndef B_H_ #define B_H_ #include "a.h" int funcB(int x) {return x-1;} #endif // B_H_ // main.cc #include "a.h" #include "b.h" int main(int argc, char* argv[]) { printf("hello"); return 0; }
机上で展開してみると理解できるかと思いますが、最初のa.hのincludeによってA_H_が定義されるので、2回目にa.hがincludeされる場所ではfuncA
の定義が記述されなくなります。
// #include "a.h" -> int funcA(int x) {return x+1;} // #include "b.h" -> // #include "a.h"は、ifndefによって何も無いファイルになる int funcB(int x) {return x-1;} int main(int argc, char* argv[]) { printf("hello"); return 0; }
以上が重複定義の意味と、#ifndef
による回避のおまじないです。
今回のケースでは、b.hに対しておまじないをする必要はありませんが、いざというときのために予めおまじないを入れておくのが慣例です。
投稿2016/08/22 00:37
総合スコア247
0
同一のプロトタイプ宣言であれば複数回書かれてもエラーとはなりません。
変数もextern宣言であれば複数あってもエラーとはなりません。
- 宣言なので平気
int funcA(int,int); int funcA(int,int); int funcA(int,int); static const int CONSTA; static const int CONSTA; extern int P; extern int P; int main(void){ int P = 10; funcA(P,2); return 0; } int funcA(int x,int y){ return x + y;}
- 多重定義だからエラー
struct { int A; int B; } struct_x; struct { int A; int B; } struct_x; int funcA(int,int); const int CONSTA = 1; const int CONSTA = 1; enum { ENUMA, ENUMB, ENUMC }; enum { ENUMA, ENUMB, ENUMC }; int P = 10; int P = 10; int main(void){ funcA(P,ENUMB); return 0; } int funcA(int x,int y){ return x + y;} int funcA(int x,int y){ return x + y;}
上の例ではすべてソースで書いていますが、ヘッダに分割していた場合もそのヘッダに定数定義や構造体定義が含まれると、二重にインクルードされるときにエラーとなります。
また、最初の例でも1回書けばいいものをエラーにならないからと何度も書くのは無駄です。
ということで、ヘッダが二重に読み込まれないように、
#ifndef
, #define
マクロを使ってヘッダが読み込まれたことを定義し、二回以上展開されないようにします。
// sample.h #ifndef _SAMPLE_H_ #define _SAMPLE_H_ 1 struct { int A; int B; } struct_x; int funcA(int,int); const int CONSTA = 1; enum { ENUMA, ENUMB, ENUMC }; #endif
#include <stdio.h> #include "sample.h" // ヘッダ内の #defineがなければ以下はエラー #include "sample.h" #include "sample.h" #include "sample.h" #include "sample.h" int main(void){ int P = 10; funcA(P,ENUMB); return 0; } int funcA(int x,int y){ return x + y;}
投稿2016/08/22 00:16
総合スコア2604
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。