いろきゅうの(元)はてなダイアリー

はてなダイアリーから移行中…

staticメンバが怪しい…?2

一昨日のログの「staticメンバのを含むクラスがリンクエラーになる」問題の続きです。今日も長ーーーくなります。すんません。^^;

さて、大学の部活で同期で、且つ、部の真の支配者「鳥の夢」氏から、コメントで突っ込みが入りまして


[引用]
(前略).hだけに変更(static constの値を変える)した時点では、そのオブジェクトファイルをコンパイルし直さないのではないかと思います。(後略)
[/引用]
(詳細なコメントは、上の「昨日のログ」のリンクをクリックしてください。)

との事。とりあえず、コメントを見て「そういえば、どうして、リンクエラーだけ見てて、コンパイル結果を全く見てなかったんだろう…」と反省。
今回は、出力ウィンドウの結果も踏まえて問題を捉えようと思います。


とりあえず、前回のコードです。今回は、テーブルを使ってちょっと見やすく書いてみます。ちなみに、何方かのブログを参考にしてます。
>何方か、ありがとうございました。^^;

//-- t00.h -----
#pragma once

class
A
{
 
static const int n; // *1
public:
 
int Get();
};
//-- t00.cpp -----
#include "stdafx.h"
#include "t00.h"

const int
A::n = 9;  // *2
int
A::Get()
{
 
return n;
}


// -- main
int
main()
{
  A a;
  a.Get();
};
//-- t00a.h -----
#pragma once
#include
"t00.h"

class
C
{
public:
 
int Get();
};
//-- t00a.cpp -----
#include "stdafx.h"
#include "t00a.h"

int
C::Get()
{
  A a;
 
return a.Get();
}

t00.h が重要なファイルになります。t00.cpp と t00a.h から include されています。

さて、前回の番号どおりにコードを可変していき、コンパイル結果をコピペしてみます。突然3から始まりますが、まぁ気にしないよう。^^;


3、普通にビルドする。
-----------------------
コンパイルしています...
t00a.cpp
t00.cpp
コードを生成中...
リンクしています...
-----------------------
これはOKですね。


4、*2 の "= 9" を *1 に持っていく。すなわち、ヘッダファイルを変更します。
-----------------------
コンパイルしています...
t00.cpp
コードを生成中...
スキップ中... (関連する変更は検出されませんでした)
t00a.cpp
リンクしています...
-----------------------
コンパイルしてねーじゃねーか!
t00a.cpp -> t00a.h -> t00.h と関係しているはずなのに、なぜコンパイルしませんか… orz


5、*1 の "= 9" の数字を変える。 
-----------------------
コンパイルしています...
t00a.cpp
コードを生成中...
スキップ中... (関連する変更は検出されませんでした)
t00.cpp
リンクしています...
t00a.obj : error LNK2005: "private: static int const A::n" (?n@A@@0HB) は既に t00.obj で定義されています。
t00a.obj : error LNK2005: "private: static int const A::n" (?n@A@@0HB) は既に t00.obj で定義されています。
Debug/t00.exe : fatal error LNK1169: 1 つ以上の複数回定義されているシンボルが見つかりました。
-----------------------
またコンパイルしてねーじゃねーか!
もー… orz


6、*2コメントアウト
-----------------------
コンパイルしています...
t00.cpp
リンクしています...
-----------------------
t00.cpp しか編集してないのでこれは当然ですね。でも、A::n を削ってるんで存在しないはず…?でも、リンクが通る…^^; ん〜。ちょっと思うところが。(下記参照


7、*2 を戻す
-----------------------
コンパイルしています...
t00.cpp
リンクしています...
-----------------------
やっぱり t00.cpp しか編集してませんからね。…でも「6」と絡めるとちょっと腑に落ちない。


8、*2コメントアウト*1の初期化をやめてしまう。
-----------------------
コンパイルしています...
t00.cpp
コードを生成中...
スキップ中... (関連する変更は検出されませんでした)
t00a.cpp
リンクしています...
-----------------------
お兄ちゃん、また飛んでるよッ! orz
「4」と同じ理由でコンパイルされるべきだとは思うんですけどねぇ…


9、*2コメントアウトを戻す。
-----------------------
t00.cpp
t00.cpp(4) : error C2734: 'A::n' : const として宣言されていますが、初期化されていません。
t00.cpp(4) : error C2734: 'A::n' : const として宣言されていますが、初期化されていません。
-----------------------
初期化されないというエラーメッセージは妥当だろうなぁ…。じゃぁ、何で「8」が通るのかと、小一時間。


とまぁ、こんな感じです。とりあえず、皆から参照されてるヘッダーファイルを弄くってるのに、コンパイルされないファイルがあるということは、ほぼ確定ですかね。
…いやまぁ、きっと理由があるんでしょうけど……。



で、前回は、思うところがありまして「staticなメンバ変数をクラス定義内で初期化した場合、インスタンスを作っちゃいけないような気がしてきました」と書きました。…ちゅーか、今見てみたらこれ間違ってるな…。

static const なメンバ変数をクラス定義内で初期化した場合、インスタンスを作っちゃいけないような気がしてきました。

が正解です。^^;
で、何故にこう思ったかと言いますと…例えば、t00.h に以下のコードを追加したとしましょう。

const int g_na = 10;
int g_nb = 5;

これでコンパイルした場合、g_nbは複数定義されることになってリンクエラーになりますが、g_na についてはエラーにならないんですよね。正確な理由は知りませんが、コンパイル時定数についてはこんな感じで定義できます。
もし、g_nb をいろんなところから参照可能な変数にしたい場合は

// t00.h
extern int g_nb;

// t00.cpp
int g_nb = 5;

として、インスタンスを生成しなくちゃいけないワケですよ。


で、コレを踏まえると…。
static const なメンバ定数(?)を、ヘッダーファイルで定義した場合、自前でインスタンスを作ってはいけないのでは?と思ったわけです。少なからず、

// t00.h
extern const int g_na = 10;
or
const int g_na = 10;

// t00.cpp
const int g_na;

と書くとC2370コンパイルエラーになります。つまり、自前でインスタンスを用意する必要が無いワケです。…とはいえ、コンパイルエラーの内容としては「自前で作るな」という意味とはかけ離れてたりしますが…^^;
(ちなみに、externを使った場合、初期化をcppに持ってくると通る。)

…じゃぁ、メンバ定数の場合、どうして自前でインスタンスを用意しても、コンパイルエラーにならないかってのが問題ですが……。う〜ん……^^;


…っと、bccじゃどうなるんだろう…と思い、フリー版をDLしてテストしてみたところ、*1 で初期化した場合、*2があっても無くても変わりませんでした。また、初期化を*2に持っていった場合も問題なくコンパイルされました。

…となると、自前でインスタンス用意を用意してもしなくても良い…?あー、でもbccだしなー。あとはgcc?…環境ないじゃん… orz



……なんか、えらい長くなってしまいましたが…… とりあえず結論。

私の知識不足

そして


私の寝不足 (重要)


なんか、途中から眠くなって、ワケわからん……。かれこれ3時間ぐらいテストとかしてる気がする……。最後のほうの文章、荒れてる気がするし。^^;

まぁとりあえず、そんなところです。誰か詳しい情報もってる方よろしく御願いしますと、他人をアテにしてみる今日この頃。^^;


このネタ、いつか本家の方でまとめよう…ココじゃ見づらいよ…