さて、先ほど以下の記事を執筆した。
上の記事では、C言語でプログラム開発をする場合に、複数ファイル間でグローバル変数を宣言して初期値を定義する手法の中でも、ワテが良く使っているオーソドックスな手法を紹介した。
一方、当記事ではそれのC++版を紹介したい。
ワテの場合、最近のC++は難解過ぎて完全には使いこなせていないが、まあ、ある程度は使いこなせる。
まずは、C++の複数ソースファイル間でグローバル変数やグローバル定数を共有する手法を紹介したい。
では本題に入ろう。
C++でmain.cppとsub1.cppファイルでグローバル変数・定数の共有
何をしたいのかと言うと、下図にその概略を図解してみた。
図 C++でmain.cppとsub1.cppファイルでグローバル変数・定数の共有方法(ワテ流)
上図で青い矢印には深い意味はない。単に赤枠の説明文が関係しているソースコードを指しているだけだ。
C++でプログラム開発を行う場合には、通常は複数のソースコードに分けて作成する。
多い場合だと100ファイル以上になる事もある。
さて、ここではその実例として二つのソースファイル
main.cpp sub1.cpp
と一つのヘッダーファイル
Header.h
があり、その両者でグローバル変数と定数を以下のように宣言して初期化もしたい。
int g_i = 1; const int c_i=1000;
また、sub1.cppソースファイルの中だけで有効なグローバル変数と定数を以下のように宣言して初期化もしたい。
int s_i_sub1 = 2; const int c_i_sub1=200;
それを図にしたものが上図だ。
物凄くヘンテコな変数のネーミングかも知れないが、そこは気にせず読み進めて頂きたい。
要するに、g_i と c_i はプログラム全体で共有されるグローバル変数(グローバルスコープ)なので、先頭に g_ を付けてみた。
一方、s_i_sub1 と c_i_sub1 はファイルsub1.cpp内の関数だけで共有されるスタティック(静的)なグローバル変数(ファイルスコープ)なので先頭に s_ を付けてみた。
const定数の場合には先頭に c_ を付けてみた。まあワテ流なので、世間の標準ではないと思う。
では、その三つのファイルを紹介しょう。
Header.hの中身
以下のように定義している。
#pragma once // グローバル変数宣言と初期化 __declspec(selectany) int g_i = 1; __declspec(selectany) int g_iarr[] = { 1, 2, 3, 4 }; __declspec(selectany) double g_darr[] = { 1.0, 2.0, 3.0, 4.0 }; // グローバル定数宣言と初期化 //x extern const int c_i; //x const __declspec(selectany) int c_i = 1000; //ok extern const __declspec(selectany) int c_i = 1000; const extern __declspec(selectany) int c_i = 1000; // constとexternが必要 //ok extern __declspec(selectany)const int c_i = 1000;
冒頭で紹介したC言語版では、#ifdef ~ #else ~ #endif の手法とGLOBAL_DEFINEと言う名前のマクロ定義の有無で、externをグローバル変数の冒頭に付ける/付けないを制御すると言う小細工的手法で有った。
C++の場合にも、その手法を用いても良いのだが、Visual Studioの場合にはもっとシンプルな手法が使えるので、ワテの場合にはそれを良く使っている。
具体的に言うと、 __declspec(selectany) と言う構文だ。
上のHeader.hに示すように記述しておくだけで、後はグローバル変数を使いたいファイルでこのヘッダーファイルをインクルードするだけで良いのだ。
初期値を与えている部分もそれぞれのソースファイルで展開されるが、特にエラーする事もなく、正しく期待通り初期化出来る。
グローバル変数の場合とグローバル定数の場合とで、多少記述方法が異なるようだ。
グローバル定数の場合には、上のコードに示すように、何通りかの記述方法がある。まあどれでも良いみたいなので好きな奴を選べば良いだろう。
MSDNでselectanyの使い方を見たい人はここに説明がある。
まあ、英語なのでワテには良く分からん。
main.cppの中身
#include "Header.h"; #include <stdio.h> int func1(int* pi); int main() { int i = 2; int result = func1(&i); printf("result=%d\n", result); // 1492 result = func1(&i); printf("result=%d\n", result); // 1192 int iend = sizeof(g_darr) / sizeof(g_darr[0]); // =4 for (i = 0; i < iend; i++) { printf("g_darr[%d]=%20.15f\n", i, g_darr[i]); } return 0; }
このmain.cppに関しては、先に説明したmain.cと全く同じである。
ただし、main.cでやっていた冒頭の #define GLOBAL_DEFINE 文はもう必要ないので削除している。
sub1.cppの中身
こちらはsub1.cと完全に同じだ。
#include "Header.h" static int s_i_sub1 = 2; static const int c_i_sub1 = 200; int func1(int* pi) { static int s_i_func1 = 3; const int c_i_func1 = 30; int result = c_i*g_i + c_i_sub1*s_i_sub1 + c_i_func1* s_i_func1 + *pi ; s_i_func1 -= 3; s_i_sub1--; *pi = -8; return result; }
ファイルの拡張子 .cpp .h .hpp などに付いて
Visual Studio2017のC++空プロジェクトを選んで上の三つのファイルを保存する。
拡張子 .cpp で保管するとそれはC++の文法で記述されていると解釈されてC++コンパイラーに掛けられる。
なお、C++のヘッダーファイルの拡張子は .hpp や .h のどちらも使われるが、Visual Studioの場合にはどちらでも良い。特にどちらを使えば良いなどの決まりは無いみたいだ。
gccなどの他のコンパイラーはどうなのかな?
その辺りはワテは詳しくない。
なお、C++でテンプレート関数を使う場合には、通常は宣言と実装をヘッダーファイルに書く必要があるが、そう言う場合には拡張子 .hpp を好む人もいるようだ。
ワテもそうする。
でも、ワテの場合には、.hppにはテンプレート関数宣言を書いて、テンプレート関数の実装はソースファイル .cpp に書く方が好きなので、そうしている。
ただし、その辺りのやり方は良く忘れるので、また機会があれば別の記事にまとめたいと思う。
実行結果
実行してみよう。
result=1492 result=1192 g_darr[0]= 1.000000000000000 g_darr[1]= 2.000000000000000 g_darr[2]= 3.000000000000000 g_darr[3]= 4.000000000000000 続行するには何かキーを押してください . . .
デバッグ無しで開始(CTRL+F5)を押すと実行出来る。
この辺りは、先に説明したC言語版と全く同じ結果になる。
まとめ
この記事では、C++でプログラミングをする場合に、複数ソースファイル間でグローバル変数やグローバル定数を共有して利用する手法を紹介した。
ワテの場合には、最近では専らVisual Studio 2017での開発が多い。
今回紹介した __declspec(selectany) の構文を使う手法はVisual Studioでは使えるが、他のコンパイラーgccなどではどうなのかは未確認だ。
機会が有れば調査したい。
C++の場合には、今回紹介したC++専用の手法でも良いし、あるいは、先に紹介したC言語版のGLOBAL_DEFINE方式を用いても良い。
まあ好き好きなのであるが、ワテの場合には、最近ではC言語では無くてC++を良く使う。
あるいは殆どC言語の文法しか使わない場合でも、ファイル拡張子を .cpp にしておくとC++特有の各種便利機能を使う事が可能だ。
なので、ワテの場合にはCでもC++でも取り敢えず拡張子は .cpp にするようにしている。
コメント