1つだけ値が有効な変数群を作成する
まえがき
前回の記事からこの記事を書くまでにメインマシンに入れいているVisual Studioがバグってしまった。
具体的には起動時、VC++のテンプレートが見当たらなくなってしまった。それに基づき、C++のプロジェクトソリューションも正常に開くことが出来なくなってしまった。
Visual Studioフォーラムにも質問したが、望む回答が得られなかった。
この問題はVisual Studioを一旦完全にアンインストールし、再度Visual Studioをインストールすることで解決した。
下記記事が参考になった。
Visual Studio Uninstaller でvisual studio 2015 を完全アンインストール - Qiita
貝柱の環境では何回かexeを起動しなおさなければ完全にアンインストール出来なかった。
さて、前回に引き続き、今回の記事も万人向けとは言いがたい。
内容が中々にただC++で遊んでいるだけのものだが、暇な人はお付き合い願いたい。
動機
Windowsのコントロールの中にラジオボタンというものがある。
例えば、A B Cなどというラジオボタングループがあったら、そのウチ一つしか選べないようなコントロールだ。
これを管理しようと思うとどうなるだろうか?
例えば、以下のように単純に配列で管理する。
bool radio[3] = {true, false, false};
この場合、配列へのアクセスを管理する関数を別で用意して、配列の中のどれか一つだけをtrueにする。
しかし、C++を使っているならばクラスとして管理したい。
クラスとして管理すれば、アクセサを介するのでどれか一つがtrueになる事は保証できるだろう。
配列ならば管理も容易だ。
ラジオボタンの意味を持たせながら変数名を付けたいときはどうだろうか?
//ラジオボタン群... bool chocolate = true; bool coockie = false; bool syrup = false;
変数名に意味合いがあると可読性がぐんとあがる。
だが代わりに管理が面倒だ。欲をいえば、例えば上記の例ならば、
coockie = true; //これをした時点で assert( chocolate == false ); assert( syrup == false ); // これが満たされて欲しい
これを実現する。
アプローチ
以下にコードを示す。
#include <iostream> #include <vector> #include <functional> #include <memory> //型を梱包するもの template<typename Ty> class FlaggedType { public: //ctor FlaggedType() { setter = &FlaggedType<Ty>::set; } virtual ~FlaggedType() = default; //=operator FlaggedType<Ty>& operator=(const FlaggedType<Ty>& other) { *this = other.val_; return *this; } FlaggedType(const FlaggedType<Ty>& other) { *this = other; } FlaggedType(const FlaggedType<Ty>&& other) { val_ = std::move(other.val_); ptr_ = std::move(other.ptr_); initializable_ = std::move(other.initializable_); } operator Ty() { return val_; } Ty& operator=(const Ty& val){ //setterを経由した代入 if (setter) { setter(*this, *this, val); } return val_; } //setter void set(FlaggedType<Ty>& base, const Ty& val) { base.val_ = val; } void only_set(FlaggedType<Ty>& base, const Ty& val) { //set処理によって値が変更される恐れがある //ダサいが一旦値をコピーしておく Ty value = val; //他の変数の値を初期化しつつセット for (auto& ele : base.ptr_) { ele->val_ = ele->initializable_; } base.val_ = value; } //setterを格納するstd::function std::function<void(FlaggedType<Ty>, FlaggedType<Ty>&, const Ty&)> setter; //private: //実際に自分が格納している値 Ty val_; //自身以外の他の変数ポインタを持つもの std::vector<std::shared_ptr<FlaggedType<Ty>>> ptr_; //自身が取るべき初期値 Ty initializable_; }; template<typename Ty> void makeUniqueFlag(const Ty& initializable, std::vector<std::shared_ptr<FlaggedType<Ty>>>& vec, FlaggedType<Ty>& one) { //初期化値をセットし、リソースを破壊しないようにしながらポインタ作成 one.setter = &FlaggedType<Ty>::only_set; std::shared_ptr<FlaggedType<Ty>> ptr(&one, [](FlaggedType<Ty>*) {}); //格納 vec.push_back(std::move(ptr)); //格納 for (auto& ele : vec) { ele->initializable_ = initializable; ele->ptr_ = vec; } } template<typename Ty,typename... Ts> void makeUniqueFlag(const Ty& initializable, std::vector<std::shared_ptr<FlaggedType<Ty>>>& vec, FlaggedType<Ty>& head, Ts&... flags) { head.setter = &FlaggedType<Ty>::only_set; std::shared_ptr<FlaggedType<Ty>> ptr(&head, [](FlaggedType<Ty>*) {}); //格納 vec.push_back(std::move(ptr)); makeUniqueFlag(initializable, vec, flags...); } template<typename Ty,typename... Ts> void makeUniqueFlag(const Ty& initializable, Ts&... flags) { //ptrの初期化 std::vector<std::shared_ptr<FlaggedType<Ty>>> ptr_vec; makeUniqueFlag(initializable, ptr_vec, flags...); } //Unionの解除 template<typename Ty> void resetUniqueFlag(FlaggedType<Ty>& value) { value.setter = &FlaggedType<Ty>::set; value.ptr_.clear(); value.ptr_.shrink_to_fit(); } template<typename Ty, typename... Ts> void resetUniqueFlag(FlaggedType<Ty>& head, Ts&... flags) { head.setter = &FlaggedType<Ty>::set; head.ptr_.clear(); head.ptr_.shrink_to_fit(); resetUniqueFlag(flags...); } int main(void){ FlaggedType<bool> red; FlaggedType<bool> blue; FlaggedType<bool> yellow; red = true; blue = false; yellow = false; auto show_bool = [](const bool& boolean) { return (boolean ? "true" : "false"); }; std::cout << "red:" << show_bool(red) << std::endl; std::cout << "blue:" << show_bool(blue) << std::endl; std::cout << "yellow" << show_bool(yellow) << std::endl; std::cout << "make unique flag" << std::endl; makeUniqueFlag(false, red, blue, yellow); blue = true; std::cout << "red:" << show_bool(red) << std::endl; std::cout << "blue:" << show_bool(blue) << std::endl; std::cout << "yellow" << show_bool(yellow) << std::endl; return 0; }
おそらくこのアプローチは完全ではない。
まず持つべきデータメンバをWrapしておきながらその実、publicになっており外部から容易にアクセスできる。
次におそらくsetterなどの渡し方はもう少し賢い方法があるはずだ。
実は記事の投稿が遅れたのは、このテーマを投稿しようとして解説文を考えていたが
色々と穴があり、結局良いアプローチが思い浮かばなかったというのもある(言い訳)。
たっぷりと時間が許された時に、もう少し綺麗に書いてみたいものだ。