質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.40%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

2回答

1970閲覧

【C++】main関数より前に処理を実行する方法について

donguri2022

総合スコア14

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

2クリップ

投稿2024/06/26 14:12

編集2024/06/27 06:49

前提

現在C++のデバッグ用の(ヘッダーオンリー)ライブラリを趣味で(OSSとして)作っていてます。
そのライブラリの設定コードを今までmain関数内に書くようにしていたのですが、出来ることなら開発環境かそうでないかで#includeするヘッダを分岐することで実現したい、つまりmain関数外に処理を書けないかと思っていました。

実現イメージ

今までこう書いていたものを、

cpp

1// main.cpp内 ------------------------------------------------------ 2#include "my_library.hpp" 3 4int main() { 5#ifdef DEBUGGING 6 // ここにライブラリの設定コードを書く 7 // なお、設定コードはヘッダーで定義されたグローバルなinline変数に値を代入するというもの 8 my_library::option = value; 9#endif 10 11 // 以下main()のコードが続く... 12}

こう書きたいです。

cpp

1// debug.hpp: デバッグ時に読み込むヘッダファイルかソースファイル --------- 2#include "my_library.hpp" 3 4// ここにライブラリの設定コードを書く 5my_library::option = value; 6 7 8// 以下main.cpp内 --------------------------------------------------- 9#ifdef DEBUGGING 10#include "debug.hpp" 11#endif 12 13int main() { 14 // 以下main()のコードが続く... 15}

実現方法案: コンストラクタを使う

main関数前の処理の実行を実現するために、以下の方法を考えました。

cpp

1// ライブラリ側で用意する ---------------------------------------------- 2// ※namespaceは省略します 3struct execute_before_main { 4 template<typename Func> 5 execute_before_main(Func func) { 6 func(); 7 } 8 static execute_before_main perform; 9}; 10 11// ユーザーがdebug.hppに書く処理 --------------------------------------- 12// ソースファイルに書く場合はinlineは不要 13inline execute_before_main execute_before_main::perform([]{ 14 // ここにライブラリの設定コードを書く 15 my_library::option = value; 16});

質問

この方法をあまり見たことがないのですが、この方法にはバグになりうるような部分がありますでしょうか?
もちろん、変なことをせずにmain関数内に書いた方がいいとは思いますが、あえてmain関数外にも書けるようにこの方法も用意しておく場合、どんなバグが考えられるでしょうか?
なお、実際に実装してみたところ、Clang、GCC、MSVCではきちんと動きました。

補足1: 環境について

C++17以上を想定しています。

補足2: 考えた方法に関して、バグになりうる部分がないか、自問自答

以下にバグになりうりそうな要因を自問自答してみた内容を書きます。
この考えがあっているか、もしくはこれ以外にもないか教えてくださると幸いです。

1. グローバル変数の初期化の実行順によるバグがありそう

今回の場合、コンストラクタで処理を実行するために(static)変数を定義しています。
その処理の中で、コンストラクタがまだ実行されていないグローバル変数を参照したり書き換えてしまうとバグが発生するというものです。
しかし、設定コードとして書くものは基本的にboolやenum型のグローバルなinline変数に代入するというもので、他のクラスのメンバを書き換えるみたいなことはしないので大丈夫だと思っています。
また、グローバル変数は定義順に初期化が実行されるという観点でも、ヘッダーオンリーライブラリだから、必ず定義(初期化)が先に来て、その後にexecute_before_mainのコンストラクタが呼ばれるようになっています。
また、ユーザーにもこの問題のことは伝えておいて、ライブラリの設定コード以外は書かないように注意しておきます。

2. コンストラクタで処理を実行するためだけに参照されない変数を新たに定義するのは名前空間が汚れる

ユーザーに新しく名前空間を作らせてもよいですが、execute_before_mainのstaticメンバの定義とすると名前空間を作らずに変数をネスト出来て、手軽でよいかなと思いました。

3. このライブラリは設定コードを書かなくても使用できるようにしたいが、その場合execute_before_mainのstaticメンバ(perform)は宣言のみで、定義がなくなってしまうことについて

performはODR-usedされていないので、定義がなくてもコンパイラはNDR(診断不要)。でも、NDRはコンパイラはエラーをはかないけど、未定義動作を引き起こす可能性は否定していないから避けた方がいいか?でもODR-usedされていない変数が定義されてないだけだから、実質未定義動作は起こらない気がするけど、、
もしダメな場合はstaticメンバは削除してグローバル変数を定義してもらう方針に変える。

追記: 後で思ったのですが、execute_before_mainをテンプレートにして、実体化を遅らせればいいのではと思いました。つまり以下のような形です。(これで実際に動きました)

cpp

1template<typename = void> 2struct execute_before_main { 3 template<typename Func> 4 execute_before_main(Func func) { 5 func(); 6 } 7 static execute_before_main perform; 8}; 9 10// perform変数の定義。それと同時にテンプレート引数に 11// voidを取った時のexecute_before_mainのテンプレート実体化 12// 文法の解釈が難しいですが、テンプレートの実体化をしているとだけ思ってほしいです 13// ソースファイルに書く場合はinlineは不要 14template<> 15inline execute_before_main<> execute_before_main<>::perform([]{ 16 // ここにライブラリの設定コードを書く 17});

4. 使用されないstaticメンバ変数の定義文として書いているので、最適化などでコンパイラに削除されてしまわないか

これに関しては実際に試してみましたが、手元の環境のclang++とg++のO2、O3、リンク時最適化、MSVCのO1、O2の最適化では削除されることはありませんでした。

補足3: コメントを受けて

設定コード用の変数の初期化自体はライブラリで行っています。
その値をカスタマイズしたいという場合に、main関数外で値を変えられたらなと思っています。
また、オプションの内容はユーザーの好みレベルのもので、プロジェクト全体を通して変わらない定数と考えてもらって構いません。

cpp

1// ライブラリ内 2namespace my_library { 3// このライブラリがログを出力するときの1行当たりの文字数のデフォルト値 4inline int max_line_width = 80; 5} 6 7// ユーザーコード内 8// ソースファイルに書く場合はinlineは不要 9inline execute_before_main execute_before_main::perform([]{ 10 // このライブラリがログを出力するときの1行当たりの文字数を100に変更 11 my_library::max_line_width = 100; 12});

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2024/06/26 19:44

通常のライブラリなら基本的にライブラリ内で初期化は閉じるべきだと思います。 オプションを切り替えたい何かの都合があるなら、それが何に依存して決まるものなのかを明確にしないと、静的に書くのが適切なのか判断できません。 例えばコンテキストが複数あり、あるコンテキストにはこの初期化が必要で、別のコンテキストにはこの初期化が必要というようなことがあるかもしれません。 なので、ライブラリが「どんなもの」で、それを「どんな用途に使用するものなのか」を具体的に書かずに、この手の質問をする理由が理解できません。 バグになりうる部分がないか、自問自答したことの答え合わせをしたいのなら、コードを全公開すべきです。正直どう書いたとしても動けば大丈夫なので。動かなくなるのは、未定義動作にしたときくらいで、それは言語仕様に依存し、それを聞きたいのであれば、できる限り具体的なコードに対して聞いて下さい。でないと言語仕様参照で終わりです。 ざっくりとしたものであれば https://ja.cppreference.com/w/cpp/language/initialization になります。
donguri2022

2024/06/26 23:11

コメントありがとうございます。 すみません、その通りですね。 詳細を追加いたしました。
退会済みユーザー

退会済みユーザー

2024/06/27 01:45

> なので、ライブラリが「どんなもの」で、それを「どんな用途に使用するものなのか」を具体的に書かずに、この手の質問をする理由が理解できません。 全く記載がありません > ...動かなくなるのは、未定義動作にしたときくらいで、それは言語仕様に依存し、それを聞きたいのであれば、できる限り具体的なコードに対して聞いて下さい。... どこにも記載がありません。
退会済みユーザー

退会済みユーザー

2024/06/27 07:10

なんか無視されてしまったようなので、この辺で失礼しますw 他の回答者さんが答えてくれるでしょう。
donguri2022

2024/06/27 07:25 編集

すみません、見逃していました。 > なので、ライブラリが「どんなもの」で、それを「どんな用途に使用するものなのか」を具体的に書かずに、この手の質問をする理由が理解できません。 どのような情報があればいいでしょうか?OSSとして公開しているデバッグ用のヘッダーオンリーライブラリです。設定コードのイメージとしても補足3に書いた内容のとおりです。 正直、これ以上言ってしまうと(身)バレしてしまうので、詳細の説明は避けたいです。 > 動かなくなるのは、未定義動作にしたときくらいで、それは言語仕様に依存し、それを聞きたいのであれば、できる限り具体的なコードに対して聞いて下さい。 未定義動作等、OSSとして危ない橋はわたらないようにしたいと思っています。 ただ、発生する問題が分かっていて、それをユーザー側に伝えたうえでユーザーの管理の元使ってもらう分には問題ないと思っています。 > でないと言語仕様参照で終わりです。 それで構いません。自分が気づいていない問題はないでしょうか。 自分が考えた方法は、あまり見たことがありませんが、どんな問題が発生しうるでしょうか?自分で調べた問題も自問自答してみた結果、問題はなさそうだと結論づけました。 でも、その自分の考えはあっているでしょうか?あるいは自分が気づいていない問題はないでしょうか? というのが質問になります。
退会済みユーザー

退会済みユーザー

2024/06/27 07:30 編集

>> なので、ライブラリが「どんなもの」で、それを「どんな用途に使用するものなのか」を具体的に書かずに、この手の質問をする理由が理解できません。 > どのような情報があればいいでしょうか?OSSとして公開しているデバッグ用のヘッダーオンリーライブラリです。設定コードのイメージとしても補足3に書いた内容のとおりです。 > 正直、これ以上言ってしまうと(身)バレしてしまうので、詳細の説明は避けたいです。 OSSではないのでしょうか?意味が分かりません。 何をするライブラリかも分からないのにこうすべきなんてありませんよ。 >> 動かなくなるのは、未定義動作にしたときくらいで、それは言語仕様に依存し、それを聞きたいのであれば、できる限り具体的なコードに対して聞いて下さい。 > 未定義動作等、OSSとして危ない橋はわたらないようにしたいと思っています。 > ただ、発生する問題が分かっていて、それをユーザー側に伝えたうえでユーザーの管理の元使ってもらう分には問題ないと思っています。 「できる限り具体的なコードに対して聞いて下さい」と言ってるので、全コード載せましょう 話はそれからになります。 >> でないと言語仕様参照で終わりです。 > それで構いません。自分が気づいていない問題はないでしょうか。 構わないなら質問を削除、と言っても回答もうありますねw 以降の記述は私が書いたことに明快な返事があったときになります。
guest

回答2

0

ベストアンサー

ライブラリの設定は変数への代入によってやるという前提は動かさないものとすると、ライブラリの利用者の側で main の前に代入を実行できるのはどんな方法があるかということになります。

コンストラクタはそのひとつではありますが、もっと簡単に初期化子として出現することが許されます。

cpp

1auto foo = (my_library::option=1, 1);

といったように書けるのです。

これをマクロで

cpp

1#define option_set(x) auto dummy##__LINE__ = (my_library::option=(x), 1)

という形でまとめておけばユーザ側は単に

cpp

1option_set(1);

と書けます。


設定のための関数 (質問中の提案にある execute_before_main::perform) を書けるようにするのは筋が悪いように思います。 関数にはライブラリの設定以外のことも書けてしまうのでライブラリが用意するものとしては自由度が高すぎます。 目的とする機能があるだけでなく間違った使い方を出来ないように配慮するのはとても重要なことです。 ライブラリの役割をよく考えて役割以上の過剰な自由度は削るのが好ましいでしょう。

ヘッダひとつの構成にするというのはそれで導入が簡単になるという思惑があってのことだと思うのであまり凝った方法を考えるのも本末転倒というものです。

投稿2024/06/28 01:51

SaitoAtsushi

総合スコア5570

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

donguri2022

2024/06/28 06:55

ご回答くださりありがとうございます。 すごいです…、これがまさに私が求めていた回答です! > 設定のための関数 (質問中の提案にある execute_before_main::perform) を書けるようにするのは筋が悪いように思います。 > 関数にはライブラリの設定以外のことも書けてしまうのでライブラリが用意するものとしては自由度が高すぎます。 そうなんですよね、それも僕も気になっていました。 でもマクロでグローバル変数を定義する場合変数名が被ってしまうので2回以上実行できないと思っていたのでコンストラクタに関数を渡す方式を考えていたんですが、、まさか行番号を使うとは! 目から鱗の回答です。 ご回答くださりありがとうございます。
donguri2022

2024/06/28 07:05

ちなみに、後で見られた方のために書いておきますと、__LINE__のマクロ展開をうまくいかせるためには、もう一個マクロを挟む必要があります。 #define set_option_aux2(x, line) auto dummy##line = (my_library::option=(x), 1) #define set_option_aux(x, line) set_option_aux2(x, line) #define set_option(x) set_option_aux(x, __LINE__)
guest

0

当方,

補足2

あたりの懸念事項(?)の話等が全くわからないガチの素人なのですが……

main() よりも前に任意のコードを実施したい」みたいな話ではなくて(←示されているtemplate絡みのコードだとそういう物であるように見える),

設定コードとして書くものは基本的にboolやenum型のグローバルなinline変数に代入するというもの

ということなのであれば,素人的には,単にそのライブラリのヘッダ(?)を

C++

1// ライブラリ内 2namespace my_library 3{ 4 inline int max_line_width = 5#ifdef MAX_LINE_WIDTH 6 MAX_LINE_WIDTH; 7#else 8 80; //デフォルト値 9#endif 10}

とかなんとか書いておくのだとダメなのだろうか? とか思うのですが,どうなのでしょうか?
(不格好だからダメとか?)

使う側としてはこんな感じ:

C++

1//※ユーザが設定値を変えたい場合にはマクロ定義を書く. 2#define MAX_LINE_WIDTH (100) 3 4#include "my_library.h" //↑のライブラリのヘッダ 5 6int main(){ ... }

あるいは,ユーザがこんなヘッダでも書くとか…
(アクロバティック感がマッハすぎて,自分では決してやりたくない気がしないでもないが)

C++

1#include "my_library.hpp" 2 3#ifdef DEBUGGING 4 5int UserMain(); 6 7int main() 8{ 9 my_library::max_line_width = 100; 10 return UserMain(); 11} 12 13//Wow!! 14#define main UserMain 15 16#endif

これもう,ライブラリの方はこうしてしまって……

C++

1//(ライブラリのヘッダ) 2namespace my_library 3{ 4 struct Settings 5 { 6 int max_line_width = 80; 7 }; 8 9 //なんということでしょう! 10 extern Settings settings; //実体は定義しないという荒業!! 11}

settings はユーザに定義させる,というのは暴挙だろうか…?

C++

1//(ユーザのコード) 2 3//my_library を使うならば settings をどこかで定義してね!っていう 4namespace my_library 5{ 6 Settings settings{}; //デフォルト値で良いならこんな. 7};

投稿2024/06/27 01:54

編集2024/06/27 08:30
fana

総合スコア11893

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

fana

2024/06/27 02:56

この回答の内容が いかにクソで,役に立たなくて,「そうじゃねぇよ」っていう話なのか? ……みたいなのを私くらいの馬鹿にもわかるように論破していただけると,それがそのまま質問内容の補足情報になり得たりしないものだろうか? みたいな.
fana

2024/06/27 03:07 編集

inline int max_line_width = 80; なるインライン変数が書かれているヘッダ(?)を複数の翻訳単位がincludeしているとして…… いずれかの翻訳単位で「 main 開始前に」値を書き換えようとかしても 他の翻訳単位でも同様のことをしているかもしれず,その場合ってどうなるんですかね? 「設定値を書き換えてやろう」っていうのを複数個所からやろうとしてたら? とか, 「main開始前に」この変数の値を用いて何かする処理がどこか別の翻訳単位に書かれていたら,そこはどんな値を用いて動くの? みたいな.
donguri2022

2024/06/27 06:26 編集

すみません、返信遅れましたmm fanaさんが素人であるとは当方思っておりませんのでご安心を。 ご回答いただきありがとうございます。 そうですね、出来ればC++のライブラリとして、マクロの多用は避けたいなと思っています。 max_line_widthの他にもオプションがあるのでマクロでやるとなるとたくさんマクロが必要になりますし、、。 あと、翻訳単位ごとにもそのマクロをincludeすることに気を付けないといけないという点でもOSSとしてはあんまり使い勝手が悪いかなと思っています。 >「設定値を書き換えてやろう」っていうのを複数個所からやろうとしてたら? 自分が考えた方法だと、staticメンバ(perform)の定義は2回以上できないので、コンパイルエラーになります。 staticメンバじゃなくてグローバル変数として定義した場合は、通常の初期化の順番に従って処理されるので、以下の例だと同じ翻訳単位内なら処理1->処理2の順、翻訳単位が違えば処理1と処理2のどちらが先になるかは不定になると思います。 execute_before_main dummy([] { // 処理1 }); execute_before_main dummy2([] { // 処理2 }); >「main開始前に」この変数の値を用いて何かする処理がどこか別の翻訳単位に書かれていたら,そこはどんな値を用いて動くの? みたいな. こちらに関しては、ユーザーにこの変数をmain開始前に参照しないように伝えておく必要がありますね、、。 そうしたいと思います。
donguri2022

2024/06/27 06:56

> 自分が考えた方法だと、staticメンバ(perform)の定義は2回以上できないので、コンパイルエラーになります。 ただ、ヘッダー内に書く場合は内容が変わらなければinlineをつけることで複数回の定義が可能になります。 この場合も翻訳単位によって定義内容が違っていた場合はコンパイルエラーかNDRの規約違反かのどちらかになります。
fana

2024/06/27 08:32

(斬新すぎるのを思いついたので追記しました)
donguri2022

2024/06/27 12:21 編集

ありがとうございます。笑 そうですね、、それは自分も思いついたのですが、externを使うと必ずユーザーがsettingsを定義しないとライブラリが使えなくなってしまうので、それは出来れば避けたいなと思っています。 あでも、Settingsクラスをテンプレート化して、カスタマイズしたい場合はテンプレートの特殊化で上書きってことはできそうです。(このライブラリはほとんどテンプレート関数で構成されているので、それをうまく使えば特殊化の定義より後にSettingsのインスタンス化をさせることができそうなので。) 意外といろんな方法があるものですね…。 これもよい方法だと思いますが、出来れば現状の設計を変えない、自分の考えた方法に悪い点がないかを教えて頂けると幸いです。(すでにリリースしちゃっているので、設定方法が根本から変わるのは避けたく…)
fana

2024/06/28 01:32

んー,まぁ,回答の冒頭で述べているように,その方法に問題点があるか?という話については私にはわからないんですよね. (だったら黙ってろよ,という感じですが) ……というわけで,言語仕様的な面での問題点とかは正直わからないのですが,なんというか,その方法を使わせられる立場を想像してみると,私くらいの者には > inline execute_before_main execute_before_main::perform([]{ ... なんてのは半ば「謎の呪文」にしか見えないかも,というのが「実用上の問題」と言えるのかもなぁ,とか. (まぁ「そんなレベルの奴はそもそも相手にしてない」なら何も問題ないと言えるんだけども…) mainより先に設定を済ませたいかどうかは本来は 使う側 の都合だと思うのだけど(違うのかな?), 仮に使う側がそうしたいと思うならば,勝手にそのようにやればいいだけの話で,あえてその execute_before_main というのを使わせようとする必要性がわからない感じ. あと,ユーザが何かしらの指定せねばならないとしても,それは「コードで書く」じゃなくても良いんじゃないかなぁ,なんて思ったり. 例えばどこぞに特定の名前の INI ファイルがあれば勝手に必要なタイミングでそこから設定値を読むとか,そういうのじゃダメなのかなぁ.
donguri2022

2024/06/28 07:00

> なんてのは半ば「謎の呪文」にしか見えないかも その通りですね笑 ただ、使用方法をドキュメントに書いておけば問題ないかなぁと思っていたんですが、まさかSaitoAtsushiさんが提案してくださったような方法があったとは、、、 ベストアンサーはSaitoAtsushiさんにさせて頂きましたが、fanaさんも長い間お付き合いくださりありがとうございました。 また機会がありましたらぜひお知恵をお貸いただければ幸いです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.40%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問