スタック・オーバーフロー Asked by TsuruharaKota on October 17, 2020
test2.hに宣言されている名前空間Bでtest1.hに宣言されている名前空間Aに宣言されたクラスをメンバとしているのですが, 以下のようなエラーが出ます.
test1.hでの #include"test2.h"
を消せば上手く行くのは分かったのですが, なぜ上手く行くのかが分からないです. よろしくお願いします.
エラーメッセージ
test2.h test1.h
In file included from test1.h:3:0,
from test1.cpp:1,
from test3.cpp:2:
test2.h:7:5: error: ‘A’ does not name a type
A::K t;
^
In file included from test3.cpp:3:0:
test2.cpp: In function ‘void B::Func()’:
test2.cpp:3:2: error: ‘t’ was not declared in this scope
t.k=100;
^
test2.cpp:3:2: note: suggested alternative: ‘tm’
t.k=100;
^
tm
In file included from test1.h:3:0:
test2.h:7:5: error: ‘A’ does not name a type
A::K t;
test1.h
#ifndef test1_H_
#define test1_H_
#include<iostream>
#include"test2.h"
namespace A{
class K{
public:
int k;
void main();
};
};
#endif
test1.cpp
#include"test1.h"
void A::K::main(){
std::cout << A::K::k << std::endl;
}
test2.h
#ifndef test2_H_
#define test2_H_
#include<iostream>
#include"test1.h"
namespace B{
A::K t;
void Func();
};
#endif
test2.cpp
#include"test2.h"
void B::Func(){
t.k=100;
t.main();
}
test3.cpp
#include<iostream>
#include"test1.cpp"
#include"test1.h"
#include"test2.cpp"
#include"test2.h"
int main(){
B::Func();
}
うまく行かない理由は、先に回答されている方の書かれた通りなのですが、では、どう書くのが正しいのか、について書いてみたいと思います。
ヘッダファイル test1.h と test2.h のように複数のヘッダファイルを作っていく場合には、依存関係が単一方向になるように設計する必要があります。
test1.h の先頭で test2.h をincludeするのであれば、test2.hの方ではtest1.hをincludeしてはいけません。逆もしかりです。
今回のヘッダファイルの内容を見ると、test2.hの記載内容をコンパイラが理解するためにはtest1.hの内容が必要ですが、test1.hをコンパイルするためにはtest2.hの内容をコンパイラが先に見ておく必要はありません。
ですので、test2.hからtest1.hをインクルードするようにします。
次にソースファイルですが、ソースファイルからソースファイルをインクルードすることはしません。
この例題ではソースファイルがtest1.cppと test2.cppとtest3.cppの3つしか登場しませんが、実際のプロジェクトではソースファイルが何千とか何万というのはざらです。そのような状況では1つ1つのソースファイルを別々にコンパイルします。
そのようにして何がうれしいかというと、一部のソースに加筆や修正を行ったときに、修正した一部のソースファイルだけコンパイルすることが可能になります。
今回のtest3.cppのように中心となる1つのソースファイルから、残りのソースファイルを全部includeしてしまっては、分割した意味がなくなってしまいます。
ですので、test3.cppから他のソースをincludeするのはやめて、test1.cpp、test2.cpp、test3.cppはそれぞれ独立にコンパイルして、最後にリンクします。
コンパイル操作はたとえば次のようにします。
$ g++ -c test1.cpp
$ g++ -c test2.cpp
$ g++ -c test3.cpp
$ g++ -o test123 test1.o test2.o test3.o
こんなのを毎回手で打っていてはたまらないのでツールで自動化します。
test1.cppのソースファイルをコンパイルするのに必要なヘッダは test1.h だけですので、test1.cppからは test1.h だけをincludeします。必要ないものまでincludeするとコンパイル時間がそれだけ余計にかかります。
test2.cpp, test3.cpp についても同様です。
ヘッダもソースも、必要最小限のヘッダファイルをincludeするように設計します。
ときには、2つのヘッダに、お互いをincludeさせたくなることがあります。
典型的には、2つの構造体(struct)またはクラスが、互いを参照しているようなケースです。
たとえば、グラフ構造を表現するプログラムを書いているときに、節点(Node)と枝(Edge)のクラスがあって、Nodeの定義の中にEdgeを登場させたい、一方でEdgeの定義の中でNodeを登場させたい、ということがあったとしましょう。
コンパイラはファイルを先頭から最後まで順に処理するので、NodeとEdgeのどちらを定義するのか、決めないといけません。Nodeを先に書いたとしましょう。
そうしたら、Nodeの中でEdgeを参照したいときには、Edgeの定義をまだ読み込んでいません。
このような場合に使える技として、「定義」ではなく「宣言」を先に済ませる、というのがあります。
次のように書きます。
class Node; // Nodeの宣言。定義は後ほど登場する。
// 宣言により、"Node"がコンパイラに型名として認知される。
class Edge { // Edge の定義。Edgeの内容を記述する。
public:
// Node の宣言しか、コンパイラは見ていない。
// この状況でも可能な操作は、Nodeのポインタ型の変数の定義。
// どの型のポインタも同じサイズなので、何かのポインタ、という情報だけで、
// コンパイラは記憶領域のサイズを決定したり、コピー処理の命令を作り出せる
// それ以上のことは、何もできない。
Node *getSourceNode();
Node *getDestinationNode();
void setSourceNode(Node *n);
void setDestinationNode(Node *n);
private:
Node *source_node_;
Node *destination_node_;
// ファイルを下まで読むと Node には inward_edge_count_ なるメンバが
// 定義されていることがわかるが、この行を処理している時点ではコンパイラは
// それを見ていないので、たとえば source_node_->inward_edge_count_ = 0;
// などと、ここに書くと、コンパイルエラーになる。
};
// あとから、Nodeを定義する
class Node {
public:
int getOutwardEdgeCount();
Edge *getOutwardEdge(int edge_index);
void addOutwardEdge(Edge *e);
int getInwardEdgeCount();
Edge *getInwardEdge(int edge_index);
void addInwardEdge(Edge *e);
private:
// このNodeから出ていくedge
int outward_edge_count_;
Edge *outward_edges_[10]; // 最大10個まで記憶できる
// このNodeに入ってくるedge
int inward_edge_count_;
Edge *inward_edges_[10];
};
このような書き方をすれば、相互に参照しあう型を定義できます。
上記の2つのクラス NodeとEdgeは、互いに密接に関係していて、つねにセットで
使われるでしょうから、典型的には1つのヘッダファイルに、2つのクラスを書きます。
NodeとEdgeを別々のヘッダファイルに分けて書くことも可能です。その場合には次のようにします。
ファイル間の参照関係はあくまでも一方通行であり、どちらにどちらを参照させるのかは、設計者がしっかり決める必要があります。
C/C++はそういう言語です。
これがたとえばjavaだと相互に参照してもOKですね。コンパイラがファイルを読む読み方が、C/C++とはだいぶ違うので。
Correct answer by hideo.at.yokohama on October 17, 2020
test3.cpp
の#include
の内容を忠実に追えばわかることです。
#include<iostream> // これは質問と関係ないので追わない
#include"test1.cpp"
#include"test1.h"
#ifndef test1_H_
#define test1_H_ // test1_H_がここで定義される
#include<iostream> // これは質問と関係ないので追わない
#include"test2.h"
#ifndef test2_H_
#define test2_H_
#include<iostream> // これは質問と関係ないので追わない
#include"test1.h"
#ifndef test1_H_ // test1_H_は定義済みなので#endifまで無視される
#endif
namespace B{
A::K t; // ここより上にAに関する宣言が存在しない
void Func();
};
#endif
namespace A{
class K{
public:
int k;
void main();
};
};
#endif
void A::K::main(){
std::cout << A::K::k << std::endl;
}
#include"test1.h"
#include"test2.cpp"
#include"test2.h"
int main(){
B::Func();
}
見て明らかなように、 A::K t;
に到達した時点で、 A
に関する宣言・定義いずれもありません。ですのでエラーになります。
というように、#include
はいい感じに解決してくれる魔法のキーワードというわけではなく、愚直に指定された行に指定されたファイルの内容をインクルードする機能です。
逆に言えば、#include
結果は開発者にも正確に理解できる機能であり、test3.cppで書かれた
#include"test1.cpp"
#include"test1.h"
#include"test2.cpp"
#include"test2.h"`
のように何でもかんでも#include
すれば問題が解決できるなどと考えるべきではありません。もっとご自身の書いたソースコードに目を向けましょう。
Answered by sayuri on October 17, 2020
Get help from others!
Recent Questions
Recent Answers
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP