• ベストアンサー

宣言と定義 通用範囲やインスタンス

C++ビギナーの者です。 オブジェクト指向プログラムを見よう見まねでやって壁にぶつかっています。 わからない事が3つ程あります。 1. 「ヘッダーファイル中」と「ソースファイルの、関数・クラスの外側」と「その中」 この3箇所での、宣言や定義にはどんな制限等が存在するのでしょうか? ヘッダーに書くとダメなものが、ソースでならOKだったり混乱しています。 2. ClassAというクラスのインスタンスを、「複数の関数」で使いたい場合に  「ソースファイルの、関数・クラスの外側」 で ClassA classa; 等として、ソース内の複数の関数で利用するのは大間違いなのでしょうか? グローバル変数が何度も初期化されるトラブル等が起きています。 3. ソースファイルA側でデータを入れたvectorを ソースファイルB側で使うにはexternを付けるだけで良いのでしょうか? 手元にある参考資料から、見よう見まねでやってしまってるのですが、 C++の解説サイトにはあまり、こういった情報が載ってないのか見つけられません。 やはり書籍を買って勉強すべきだと痛感し始めたのですが、 解説のあるWebサイト、おすすめの書籍などが有りましたら それらも教えて頂けると幸いです。 よろしくお願いします っ_ _)っ

質問者が選んだベストアンサー

  • ベストアンサー
  • KDASH-XP
  • ベストアンサー率46% (63/136)
回答No.1

実際にコンパイルしたオブジェクトコードに含まれるシンボルなどをダンプしてみると、分かりやすいのですが。 >1. >「ヘッダーファイル中」と「ソースファイルの、関数・クラスの外側」と>「その中」 >この3箇所での、宣言や定義にはどんな制限等が存在するのでしょうか? >ヘッダーに書くとダメなものが、ソースでならOKだったり混乱していま >す。 ヘッダファイルに書いた宣言は、インクルードしたソースでのみ展開されますから、例えば int i; という宣言があった場合、インクルードしたソースでは常にこの変数を実装していることになります。 いわゆるグローバル変数というやつですね。 通常ヘッダファイルはifdefなどで、2重読み込みされないよう配慮されていますから、同じヘッダファイルをインクルードしているソース中に1つだけ宣言がされていることになります。 一方ソース中の関数の外、これもグローバル変数ですが、ヘッダファイルのように2重読み込みされないような配慮がされていないため、複数のソースでint iという宣言が出現してしまう可能性があります。 (※実はこの辺はソースの書き方などをプログラマがきちんと配慮してるかどうかにも依存します。) この場合、コンパイルした後、リンクする際に、Dupliacted Symbolsとかってエラーが出る場合があります(古い記憶なので、微妙かも)。 ↑の場合はexternという予約後をつけて宣言することで、どこか別の場所で宣言されている同名の変数を実体として参照する...のように宣言させることも可能です。 一方、関数の中は、常に関数スタックで参照される変数なので、関数が呼ばれるたびに作成されるため、それぞれの関数内でのみ有効です。 故にローカル変数と言います。 >2. >ClassAというクラスのインスタンスを、「複数の関数」 >で使いたい場合に > 「ソースファイルの、関数・クラスの外側」 >で ClassA classa; >等として、ソース内の複数の関数で利用するのは大間違 >いなのでしょうか? >グローバル変数が何度も初期化されるトラブル等が起きています。 何度も初期化される、ということはインスタンスが複数できているハズです。 デバッガなどで、インスタンスのアドレスを確認すると、恐らく異なるアドレスが返ってくるかと思います。 C++ではstatic をつけて宣言することで、常にインスタンスは1つしかないことが保障されます。 >3. >ソースファイルA側でデータを入れたvectorを >ソースファイルB側で使うにはexternを付けるだけで良いのでしょうか? ソースファイルA側のグローバルクラス(STLですよね?)のインスタンスを参照する場合であれば、externを使います。 そもそもstdoutとかstderrとかが一番身近なところにあるexternされている変数と考えれば、分かりやすいのではないでしょうか? ※興味半分、結構思いだしながら書いてるので、きちんと確認していただくことをお薦めします。

参考URL:
http://homepage2.nifty.com/c_lang/
zaxs5968
質問者

お礼

>1 なるほど。 ヘッダーは2重読込み防止があり、 ソース側はそれが無い事が影響する事が有るのですね。 ありがとうございますっ 頭に叩き込んでおきます。 ローカルに関しては習った通りのようで安心しました。 >2 そんな事が起きていたのですね。 ループ処理が1回ループを抜けると デバッグでは再突入してくれないので、確認は出来ないのですが、 staticで1つに出来るのですね。 値がちゃんと入ったグローバル変数が初期化されて 訳がわからなくなっていて、本当に助かりました・・っ! >3 大丈夫なようですね。 ありがとうございます。 沢山のご助言本当にありがとうございます。 たまっていた疑問が一気に吹き飛びました。感謝!

その他の回答 (2)

  • zwi
  • ベストアンサー率56% (730/1282)
回答No.3

そもそもオブジェクト指向化を目指して組んでいるのに、グローバル変数をクラスが初期化する段階で変な気が。クラスのインスタンスは複数作成できますが、それぞれのインスタンスは違う値を持つことと出来る=別のデータとして独立して扱えるように組む必要があります。同じグローバル変数を初期化している段階でインスタンスとして問題があります。 グローバルに参照可能な1つのクラスのインスタンスが値を保持するならまだ分りますが。 いくつか参考サイトを紹介しておきます。 http://www5c.biglobe.ne.jp/~ecb/cpp/cpp00.html http://www.asahi-net.or.jp/~yf8k-kbys/newcpp0.html http://www.geocities.jp/cbc_vbnet/vc/CV_main.html http://wisdom.sakura.ne.jp/programming/cpp/index.html

zaxs5968
質問者

お礼

いえ、クラスで初期化していないのに 上記の使い方で初期化が起きて困惑していました。 #1様の助言でおそらく解決できそうです。ありがとうございます。 そしてリンクもありがとうございます。 解説サイト毎に違った側面で理解できるので 出来る限りすべて一度目を通して、もっと基礎を固めようと思います。 ありがとうございました

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.2

ぶっちゃけて言えば「ヘッダファイル」とか「ソースファイル」とかいうのは「人間がそう思っている」だけであり, 処理系からしてみればいずれも「ソースファイル」でいうことになります. だから, 本質的には「ヘッダファイルの中」とか「ソースファイルの関数・クラスの外側」などというのは意味のある区別ではありません. で, 記憶領域を確保する定義は「プログラム全体で 1つ」である必要があります. 一方, 記憶領域を確保しない定義や定義でない宣言は, 「同じもの」であれば 1つのコンパイル単位に何回現れてもよいことになっています. ということで, まずは「定義」と「(定義でない) 宣言」をしっかり区別することをお勧めします. 少なくとも変数についていえば「extern があれば宣言, なければ定義」でだいたいなんとかなる.

zaxs5968
質問者

お礼

なるほど。 何か分けると別物のように思えていましたが、同じなのですね。 >記憶領域を確保する定義は「プログラム全体で 1つ」である必要 >記憶領域を確保しない定義や定義でない宣言は, >「同じもの」であれば 1つのコンパイル単位に何回現れてもよいことに この節をしっかり理解できないという事は、やはり 私のこのあたりの認識が悪いのですね。 もう1度しっかり調べなおして学習してきます。 >「extern があれば宣言, なければ定義」でだいたいなんとか externからそのまま宣言定義を1行で済ますのはまずかった・・のでしょうか; 何はともあれ、この先為になりそうな助言、ありがとうございました!

関連するQ&A

  • 定義されているのにエラーになる

    閲覧ありがとうございます。 C言語のプログラムの話です。 ソースファイルaaa.cでstdio.hをincludeしています。 stdio.hには extern FILE _iob[_NIOBRW]; というのが定義されているにも関わらず、 コンパイルすると、 aaa.o:aaa.c:(.data+0x58): undefined reference to '_iob' というエラーが出ます。 aaa.cでは_iobに関する宣言はしていませんが、それを使うこともしていません。 ヘッダファイルでexternで宣言してるからかなと思い、ソースファイルにexternなしの宣言を書き足したら、コンパイルは通りました。 今回は元々既にあるプロジェクトの改修だったのですが、宣言を消したりしていません。 しかし、その部分は元々ヘッダファイルに宣言などしなくてもコンパイルが通ってました。 昔はヘッダファイルでexternで宣言してたらソースファイルではしなくてオッケーみたいな感じだったんですかね? 私自身、あまりよく分かってなくて文章もめちゃくちゃですみません。 不足している情報があればできる範囲でお伝えしますので、ご協力よろしくお願いいたします。

  • 関数をクラス化する目安とは・・?

    C++ビギナーです。 見よう見まねですが、おおまかな機能毎に1ヘッダー1ソースを作り オブジェクト指向の真似事をやっているのですが、 新たにクラスを作るとなると、 1つの関数を作るのに定義やコンストラクタを一々作らなければならないのに対して、 クラスにせず、関数単体ならぱっと書けて 使うときも直に呼び出すだけでとても楽なのですが、 1.関数をクラス化するメリットはどのような場合に有るのでしょうか? 2.クラス化すべき場合、すべきでない場合 というのが有るのでしょうか? 3.クラス化していない関数が点在するのは良くないのでしょうか? おおまかな事で構いませんので、目安を教えて頂けると幸いです。

  • クラスのメンバ関数を別ファイルで定義したときのバグ

    C++ においてヘッダファイルで宣言したクラスのメンバ関数を別のソースファイルで定義して、コンパイルするとうまくいきません。エラーは出ないのですが、同名の何もしない関数としてコンパイルされているようなのです。クラスのメンバ関数を宣言したのと同じヘッダに書くとちゃんとコンパイルされます。 どうしてそうなるのか、いまいち原因がわかりません。

  • C言語で、記憶クラス指定子extern・staticを関数に指定

    C言語の本に、「関数の定義と呼び出す側が別ソースファイルの場合、プロトタイプはヘッダーファイルに書き、定義側と呼び出し側の両方でインクルードしましょう」ということが書かれていました。 例えば、 ===code1a.c=== extern void funcB(int); static void funcA() { funcB(1); } ===code1b.c=== void funcB(int a) { printf("%d\n",a); } このような場合には、もしcode1b.cの中の関数funcBに引数を追加した場合、再コンパイルしても気づかないのでよくない。 そこで、次のようにヘッダーファイルを作り、プロトタイプはそこに書くべきだ。 ***code2b.h*** extern void funcB(int); ***code2a.c*** #include "code2b.h" static void funcA() { funcB(1); } ******code2b.c**** #include "code2b.h" void funcB(int a) { printf("%d\n",a); } 記述は以上のようなことです。 #include "code2b.h" とは、 extern void funcB(int); が書いてあるのと同じだと思います。 私が思ったのは、本の勧める方法では、 funcBを定義しているcode2b.cで、プロトタイプの記憶クラス指定子が、externになっているが良いのか(externとは、別のソースファイルで定義されているという意味ではないか)ということです。 extern, staticは、プロトタイプに書くべきなのか、関数の定義に書くべきなのか、も両方に書くべきなのでしょうか。 私の処理系では、 ・プロトタイプ宣言でexternを付けて関数定義でstaticを付ける、 ・staticを付けた関数を他のソースファイルで呼ぶ、 などの明らかに矛盾する場合は、コンパイルエラーになります。 でも、extern単独での役割はなさそうです。 他の処理系でも同じでしょうか。 (main等省略)

  • クラスのインスタンスって何者?

    データ用クラスのインスタンスなのですが、以前 「クラス名 利用名;」 で使うと毎回別のインスタンスを呼び出すらしく値が空になるトラブルが起き 「static クラス名 利用名;」 とすれば常に同じインスタンスになるからそれを使いなさいと教えられました。 以後ずっとこれを使ってきたのですが、 ヘッダーファイルAに記述したデータクラスを ソースファイルAで「static クラス名 利用名;」 ソースファイルBで「static クラス名 利用名;」 と利用名を完全に同じにしてもエラーが出ず(ソースのヘッダ領域なのに) ソースファイルAでそのデータクラスに入れたデータは ソースファイルBでは利用できず空になっていました。 極当たり前の何かを私はわかってないから こういうトラブルが起きているんだと思うのですが、 クラスのインスタンスって何者なんでしょうか? どうすれば複数のソースから1つのデータクラスを作り上げ 全体で利用できるように出来るのでしょうか?

  • C++ データの扱い方の種類を教えて下さい

    現在、資料の見よう見まねで機能毎にソースとヘッダを複数分けているのですが、 クラスを超えてデータを管理・変更する方法がわからずに、 現在全てのソースのヘッダーで 『extern』 宣言をして共有してしまっています。 (1変数・1配列単位でちまちまと。) できれば、 1.クラスを超えて、プログラムが終わるまで扱っていたいデータ。 2.数クラスをまたいだら、削除してしまいたいデータ。 に適した方法と 「データをまとめて扱い易くする方法」を詳しく教えて頂けると幸いです。 実装方法は自分で何とか頑張って勉強してみるつもりですので、 個人的な使い勝手で構いませんので『種類と用途』をどうか教えて下さい。

  • 同一クラスインスタンス名で別クラスのインスタンス作成方法(C++)

    下記ソース(Java)の処理をするような、C++の実装方法を教えていただきたいです。 【処理内容】 クラスBのインスタンスを保持しており、クラスBのインスタンス名と同一であるクラスAのインスタンス生成 ClassA A_Instance = (ClassA)Class.forName(B_instance.name).newInstance(); 要はクラスインスタンスの名前の求め方がわからないのです。それさえわかれば、newしてクラスポインタを返すメソッドを用意すれば何とかなると考えていますが。 ※C++のAPI一覧はどこにあるのだろうか・・・

  • CからC++の関数呼び出し

    CからC++の関数を呼び出して値を取得したいのですが、コンパイルエラーになってしまいます。 呼び出す関数内でさらに同じクラスの関数を呼び出して値をreturnしたいだけなのですが、 ご教示願います。 ソースは下記のような感じです。 Cソース(AAA.c) main(){ ・・・・・ if ( funcA() ){ ・・・・ } } C++ソース(BBB.cpp) int clsB::funcB { return iflg; } extern "C" { int funcA() { return funcB(); } } ※上記externは特に何かの関数内に書いているわけではありません。 C++ヘッダ(BBB.hpp) class clsB : public XXX { private: int iflg; ・・・・ public: int funcB(); ・・・・ } 上記にコンパイルすると、 externしている箇所でfuncBはスコープにありません?のようなエラーが出てしまいます。 ラッパ関数を使って使用するなどと聞いたのですが、やり方がいまいちわかりません。 どうか教えていただけますでしょうか。 また、他にやり方があるかと思いますが、いいやり方があれば教えていただけると幸いです。 以上です、よろしくお願いいたします。 不足内容あれば指定願います。

  • CとC++、どちらでコンパイルされているかをソース中で判別する方法

    タイトルのとおり、CとC++のどちらでコンパイルされているのかを、ソース中で判別する方法(#ifなど)を教えて下さい。 目的は、extern "C" という記述がCではエラーになってしまうのですが、CでもC++でもインクルード可能なヘッダファイルにするため、ヘッダファイル中で   #if「C++でコンパイル中」     extern "C" {   #endif としたいのです。 よろしくお願い致します。

  • ヘッダファイルにおける文字列リソース定義

    C言語(主にVC++)で開発をしていると、よく、 const char MSG_HELLO[] = "HELLO!"; のように、ヘッダファイル中で、文字列リテラルを定義します。 自分もいつのまにかこのようにリソース定義するようになっていましたが、初期の頃から疑問だったのは、extern宣言せずにchar配列を宣言しているから、コンパイラが最適化しなかったら、上記宣言のあるヘッダファイルを読み込んで、その定義を使った個所のあるソース分文字列リテラルがメモリ上に確保されてしまうのではないか、、?ということです。 つまり、本来なら const char* MSG_HELLO[] = "HELLO!"; とソースファイル中で定義し、ヘッダファイルには extern const char* MSG_HELLO; とすべきではないか、と思うわけです。ただ実際にこれをやっているとヘッダとソースの両方のメンテナンスが必要になるので、冒頭のように記述しているのだと思いますが。 この、本来なら下記のようにすべきだが、コンパイラの最適化(リソースのプール)を期待して最初のように書いている、という解釈は正しいでしょうか?

専門家に質問してみよう