- 締切済み
c++でのヘッダーファイルの循環参照
c++言語においての質問です. A, Bという二つのクラスを作ったとします. 宣言は.h,実装は.cppで行っています. 【A.h】 class A{ //内容 public: B ConvertB(); }; 【B.h】 class B{ //内容 public: A ConvertA(); }; 上のように,クラスAではクラスBを返すメソッド. クラスBではクラスAを返すメソッドを実装したいとします. しかし,単純に 【A.h】に #include "B.h" 【B.h】に #include "A.h" などとすると当然循環参照になってエラーになりますよね? ...とは言っても, 【A.cpp】や【B.cpp】にインクルードした所で, ヘッダ側でエラーが起きてしまいます. そこで,私の場合は... 【A.h】の先頭に class B{ConvertAで用いるメソッド}; 【B.h】の先頭に class A{ConvertBで用いるメソッド}; というようにする事でなんとかエラーを避けています. ...しかし,この方法ではAやBのクラスの定義を修正する場合に, 2つのファイルのヘッダを書き換えなければならなくなります. A, B, C, D...などとクラスが増えていくと, 一つのヘッダーファイルに4つも5つもクラスの定義を書かなければなりませんし, クラスのメソッドの定義を一つ変えようとしただけでも, 複数のヘッダの内容をいじらないといけません. 非常に読みにくいコードになってしまうのです. そこで,もう少しスマートに実現する方法は無いでしょうか? JavaやC#を使えば簡単に解決するのですが...ライブラリの関係でC++を使わなければ ならないのです. もしくは,このような相互変換?のクラスを作る場合はみなさんはどのようにして ヘッダーファイルの循環参照を避けているのでしょうか? 例えば様々な形式の色空間のクラスなどだったら... RGBクラス YCrCbクラス HSVクラス ...など このようなクラスを作った後で,RGBをYCrCbに変換出来るようにしたり, その逆へも変換出来るようにしたいのです. それともこのような変換が出来るクラスを作る事は間違っているのでしょうか? よろしくお願いします.
- みんなの回答 (6)
- 専門家の回答
みんなの回答
- Tacosan
- ベストアンサー率23% (3656/15482)
結局のところ「ヘッダに書いてある前方宣言に,さらにコンストラクタやメソッドなどまで定義してしまっていて」というところが問題になってるだけ? もしそうなら, 「ヘッダに書かなければいい」だけじゃないの?
- Tacosan
- ベストアンサー率23% (3656/15482)
とりあえず #1 は書いたんだけど, 実のところ「クラスのメソッドの定義を一つ変えようとしただけでも,複数のヘッダの内容をいじらないといけません.」が何を言っているのかさっぱりわからなかったのでそこは無視してたりします. 具体的に「こんな風に困る」ってのがあればコメントするかも. で, 私も #3 同様何か構成が不自然なんじゃないかなという気はしました. それが「クラスの作り方」なのか, 「ファイルの作り方」なのかはちょっとわかりません. 例えば, 「『相互変換』に意味がある」ということは, それらのクラスは本質的に同じものを表している可能性があります. だとしたら, その「本質的に同じもの」を切り出すべきなのかもしれません. あるいは「本質的に同じものなんだから 1つのファイルで宣言/定義する」というのも 1つの考え方.
- akayoroshi
- ベストアンサー率50% (46/91)
ヘッダファイルに宣言だけを含めるのであれば // AB.h class A; class B; class A{ public: B convert(); }; class B{ public: A convert(); }; を作ればA, Bの定義や引用で共通に使えます。 // A.cpp #include "AB.h" B A::convert(){ return B(); } // B.cpp #include "AB.h" A B::convert(){ return A(); } // main.cpp #include "AB.h" int main(void) { A a; B b=a.convert(); a=b.convert(); }
お礼
「一つのファイルに一つのクラス」という呪縛にとらわれていました>< 確かに,一つのファイルに複数のクラスを書いても良い場合は結構ありそうですね. それも視野に入れて,一度考え直してみます. 回答ありがとうございました^^
- 麻野 なぎ(@AsanoNagi)
- ベストアンサー率45% (763/1670)
おそらくは、クラスの作り方が適切でないという可能性が高いです。 本来、クラスの循環参照は、単純に、クラスの「前方宣言」をつかうことで解消できるレベルに抑えられるようになっています。 ここで、質問にあるように、 > 【A.h】の先頭に class B{ConvertAで用いるメソッド}; > 【B.h】の先頭に class A{ConvertBで用いるメソッド}; という、calss B; だけでなく(これだけを宣言しておくのが、前方宣言です)メソッドまで宣言しなければ、やりくりできないというのは、良い設計ではないです。 基本的に、クラスの、メンバ関数は、「自分のクラスの中にある情報だけで、処理ができるもの」であるべきです。 もちろん、例外はありますが、基本はそういうことで。 この意味で、クラスというのは、「とにかく関わりのありそうな処理をぜんぶまとめておく」というものではないです。 で、この場合、単純に、 A converA(B var); という関数を、クラスの外に定義すれば、非常に見通しが良くなると思いますが。 covnertA() を、Bのメンバ関数にするのは、Bのメンバだけで、すべて処理ができるときに限定した方が、見通しは良くなります。 なお、クラスのメンバ関数にしなくても、例えば、 A convertA(B var); A convertA(C var); と、いろいろなクラスから、A に、converA() という同じ名前の関数で変換できるような関数を定義するのは、C++ で可能であることに注意してください。 また、convertA(B var); が、class B の private: メンバーにアクセスする必要があるなら、covnertA() を、classB の friend 関数にすればOKです。
お礼
このごろJavaやC#を用いる事が多かったもので,クラスの外で関数を使う事を極端に嫌っていました>< C++でこのような循環参照がよく起るのは,逆にこのような場合は外で関数を使いましょうと言う事なのかもしれないですね>< 一度構造を見直してみます. 前方宣言だけで解決出来る方法も同時に考えてみたいと思います. 回答ありがとうございました^^
- qwertfk
- ベストアンサー率67% (55/81)
----------------------------------- A.h ----------------------------------- #pragma once class B; class A { public: B b(); }; ----------------------------------- B.h ----------------------------------- #pragma once class A; class B { public: A a(); }; ----------------------------------- A.cpp ----------------------------------- #include "A.h" #include "B.h" B A::b() { return B(); } ----------------------------------- B.cpp ----------------------------------- #include "B.h" #include "A.h" A B::a() { return A(); }
お礼
私も現在の所,これに似た方法をつかっています^^ ただし,ヘッダに書いてある前方宣言に,さらにコンストラクタやメソッドなどまで定義してしまっていて... もう少しすっきり出来るように構造を考えてみます^^ 回答ありがとうございました^^
- Tacosan
- ベストアンサー率23% (3656/15482)
「インクルードガード」とか「#pragma once」とかで調べてみるといいよ.
お礼
インクルードガードは一応しているのですがなかなか解決しなくて>< 回答ありがとうございました^^
お礼
やっぱりクラス設計が悪そうですね>< こんな回りくどい方法でしかエラーを回避出来ない事自体で,構造を見直すべきなのかもしれません>< 「本質的に同じもの」というのは確かに似ているクラスもあるので,継承をうまく使えばスマートな方法があるかもしれませんね. 一度考えてみます 回答ありがとうございました^^