マサカリ募集中です。
どうぞお気軽に投擲していってください。
※ 2019/3/11 一部更新しました。
例
こんなの
#include <iostream>
#include <string>
#include <functional>
#include <array>
enum class event{
event1,
event2,
end
};
void func1(std::string && str){std::cout << "[func1:]" << str << std::endl;}
void func2(std::string && str){std::cout << "[func2:]" << str << std::endl;}
void (*funclist[static_cast<int>(event::end)])(std::string && str) = {
func1, //event1の値を元に呼び出したい関数
func2 //event2の値を元に呼び出したい関数
};
int main(){
funclist[static_cast<int>(event::event1)]("event");
funclist[static_cast<int>(event::event2)]("event");
}
これを格納順番に依存しないようにしつつ、かつエレガントに記述したい!
どうするか
パッと思いつくのは以下の方法
- enum定数と関数ポインタを同時に保持して呼び出しの都度探索
- 実行時に配列へ関数ポインタを挿入する関数を作成し実行時に毎回呼び出し
- 諦めて拡張の都度順番を確認しながら挿入していく
1,2はどうしてもコストがかかってしまうのであまり用いたくありません。
と言う訳で諦めて3に行ってしまう前に、C/C++でどのようにすれば解決できるかを考えていきたいと思います。
補足(12/2追加)
コメントで教えていただきましたが、設計図を元にツールでコードの自動生成を行うと言った方法を取るという選択肢もあるようです。
より良いシステム開発のために、状態遷移設計のことを知ってほしい - Qiita
ツールで自動化出来るようなシチュエーションであれば作って運用するのが一番安全でてっとり早いかもしれません。
Cの場合
C99なら指示付き初期化を使うことによって解決します。
#include <stdio.h>
enum event{
event1,
event2,
event3,
event4,
end
};
void func1(const char * str){
printf("[func1]%s\n",str);
}
void func3(const char * str){
printf("[func3]%s\n",str);
}
void (*eventlist[end+1])(const char *) = {
[event1] = func1, //event1の値を元に呼び出したい関数
[event3] = func3, //event3の値を元に呼び出したい関数
[end] = NULL,
};
//歯抜け対策用に一枚かませる
inline void callEvent(enum event ev,const char *str);
int main(void){
callEvent(event1,"event");
callEvent(event2,"event");
callEvent(event3,"event");
callEvent(event4,"event");
return 0;
}
void callEvent(enum event ev,const char *str){
if(ev >= 0 && ev < end && eventlist[ev]){ //範囲外or関数ポインタ未挿入であれば実行しない
eventlist[ev](str);
}
}
ですがC++では配列の指示付き初期化は定義されておらず、規格の上では使用することができません。1
C++の場合
constexprコンストラクタを利用して初期化します
#include <iostream>
#include <string>
#include <cstdint>
enum class event : uint32_t{
event1,
event2,
event3,
event4,
end
};
void func1(std::string && str){std::cout << "[func1]" << str << std::endl;}
void func3(std::string && str){std::cout << "[func3]" << str << std::endl;}
struct func{
constexpr func() : eventlist(){
eventlist[static_cast<uint32_t>(event::event1)] = func1; //event1の値を元に呼び出したい関数
eventlist[static_cast<uint32_t>(event::event3)] = func3; //event3の値を元に呼び出したい関数
}
//歯抜け対策用に一枚かませる
void callEvent(event ev,std::string && str) const{
auto val = static_cast<uint32_t>(ev);
if(val < static_cast<uint32_t>(event::end) && eventlist[val]){ //範囲外or関数ポインタ未挿入であれば実行しない
eventlist[val](std::move(str));
}
}
private:
void (*eventlist[static_cast<uint32_t>(event::end)])(std::string && str);
};
constexpr func f;
int main(){
f.callEvent(event::event1,"event");
f.callEvent(event::event2,"event");
f.callEvent(event::event3,"event");
f.callEvent(event::event4,"event");
}
以上のコードはC++14以降で動きます。
ラッパークラスを作ったり関数の呼び出し毎にオブジェクト名を記述する手間が増えますが、
上で挙げた3つの方法を採用するのに比べれば安いものでしょう
またC++17以降であれば関数の代入時にラムダ式を利用することもできます。
#include <iostream>
#include <string>
#include <cstdint>
enum class event : uint32_t{
event1,
event2,
event3,
event4,
end
};
struct func{
constexpr func() : eventlist(){
eventlist[static_cast<uint32_t>(event::event1)] = [](std::string && str){std::cout << "[func1:]" << str << std::endl;}; //event1の値を元に呼び出したい関数
eventlist[static_cast<uint32_t>(event::event3)] = [](std::string && str){std::cout << "[func3:]" << str << std::endl;}; //event3の値を元に呼び出したい関数
}
//歯抜け対策用に一枚かませる
void callEvent(event ev,std::string && str) const{
auto val = static_cast<uint32_t>(ev);
if(val < static_cast<uint32_t>(event::end) && eventlist[val]){ //範囲外or関数ポインタ未挿入であれば実行しない
eventlist[val](std::move(str));
}
}
private:
void (*eventlist[static_cast<uint32_t>(event::end)])(std::string && str);
};
constexpr func f;
int main(){
f.callEvent(event::event1,"event");
f.callEvent(event::event2,"event");
f.callEvent(event::event3,"event");
f.callEvent(event::event4,"event");
}
まとめ
と言う訳でパッと思いついた方法を取らずになんとか順番に依存しない初期化を行うことができました。
と言っても個人的にはなんかモヤモヤする点もあるのでもっといい方法があれば是非教えてください。
参考資料
- 【C++】静的配列をコンパイル時に動的生成する方法:http://marycore.jp/prog/cpp/constexpr-array/
-
規格では定義されていないようですが、一部の主要コンパイラではC++でも記述できるようです。 ↩