windows.hが必要ないwindows GUIアプリケーションの作り方
概要
int WINAPI WinMain
というエントリポイントにも関わらずヘッダが必要な関数があります。
コンソールアプリケーションでプロジェクトを作るとint main()
から始める事ができるが、アプリケーションの動作中ずっと不要なコンソールが出現したままになります。
解決策
まず、Windowsアプリケーションでプロジェクトを作成します。
次にConfiguration Properties -> Linker -> Advanced -> Entry Point
にmainCRTStartup
と書き込みます。
これでint main()
で始める事ができて、なおかつ不要なコンソールが出現しません。
Concept Lite(軽量コンセプト)で出来る事
概要
gcc6.0にてConcept Liteのコンパイルができるようになったので、色々と触ってみました。
wandboxを利用すれば簡単に試す事ができます。
[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ
書き方
conceptはconstexprな関数や変数としても解釈されるので以下の書き方は両方同じ意味になります。
template<class T> concept bool Pointer = requires(T a){ *a; }; // 制約を満たす場合はtrueとなる変数templateとしても機能する bool a = Pointer<int*>;
template<class T> concept bool Pointer(){ return requires(T a){ *a; }; } // 制約を満たす場合はtrueとなる関数templateとしても機能する bool a = Pointer<int*>();
コンセプト
コンセプトは型として使う事ができます。
型はautoの様に予測され、制約を満たさない場合はエラーとなります。
template<class T> concept bool Pointer = requires(T a){ *a; }; // concept 'Pointer<int>' was not satisfied Pointer a = 1; Pointer b = "test"; // OK
関数の引数に制約を加える場合は以下の記述が可能です。
// 直接指定 void F(Pointer){} // requires-expression template<class T> requires Pointer<T> void F(T) {} // 型指定の代わりにコンセプトを記述 template<Pointer T> void F(T){} // concept-introduction Pointer{T} void F(T){}
simple requirement
template適用後の記述内容にエラーが見つかると制約を満たさなくなります。
template<class T> concept bool Printable = requires(T a){ std::cout << a; };
compound requirement
{}
で囲まれた演算の結果の型制約を定義できます。
template<class T> concept bool Compound = requires(T a){ {a} -> const char*; {*a} -> char; {int(*a)} -> int; }; int main(){ Compound a = "test"; }
type requirement
必要な型を制約に加える事ができます。
template<class T> concept bool Type = requires(T a){ typename T::type; }; struct X{ using type = int; }; int main(){ Type a = X(); }
以下の記述だとコンセプトを満たしません。
concept bool Type = requires(T a){ T::type; }; struct X{ using type = int; }; int main(){ // concept 'Type<X>' was not satisfied Type a = X(); }
nested requirement
複数のコンセプトを要求する場合に使います。
// 中身が空はエラーなので書いているだけで、0に意味はない template<class T> concept bool A = requires(){ 0; }; template<class T> concept bool B = requires(){ 0; }; template<class T> concept bool AB = requires(){ requires A<T>; requires B<T>; };
コンセプトを満たさない型
struct noncopyable { noncopyable() = default; noncopyable(const noncopyable&) = delete; noncopyable& operator=(const noncopyable&) = delete; }; //単純に!で否定するとコンセプトを満たさない表現が可能 template<class T> concept bool Noncopyable = !requires(T a){ a = a; }; int main(){ noncopyable a; Noncopyable &b = a; }
継承関係
継承関係での分岐も簡単に書けます。
struct A; struct B; template<class T> concept bool IsA = requires(T a){ static_cast<A*>(&a); }; template<class T> concept bool IsB = requires(T a){ static_cast<B*>(&a); }; struct A{}; struct B{}; struct AB: public A, B{}; int main(){ static_assert(IsA<AB>); static_assert(IsB<AB>); }
mixinの型重複の検出
struct A; struct B; struct C; template<class T> concept bool IsA = requires(T a){ static_cast<A*>(&a); a.AF(); }; template<class T> concept bool IsB = requires(T a){ static_cast<B*>(&a); a.BF(); }; template<class T> concept bool IsC = requires(T a){ static_cast<C*>(&a); a.CF(); }; template<class T> concept bool IsABC = requires(){ requires IsA<T>; requires IsB<T>; requires IsC<T>; }; struct A{ void AF(){} }; struct B{ void BF(){} }; struct C{ void CF(){} }; struct AB: public A, B{}; struct BC: public B, C{}; // ここでAB::BFとBC::BFがb.BF()として呼ばれ曖昧なのでエラーが出るのでコンセプトIsABCを満たさない struct ABC: public AB, BC{}; // しかしエラーはいつものconcept 'IsABC<ABC>' was not satisfiedなので原因は分かりにくい int main(){ IsABC abc = ABC(); }
型となる可能性があるのでメタ関数の命名規則は使わない方が良さそうですね。
余談
何の問題か分かりませんが以下のコードでinternal compiler errorが出ます。
template<class T> concept bool Compound = requires(T a){ {++a} -> T&; }; int main(){ Compound a = 0; }
参考
pacmanでglfwを入れる
pacmanでライブラリも入れられるのに気付いていませんでした。
# glfwのパッケージを探す $pacman -Ss glfw mingw32/mingw-w64-i686-glfw 3.1.1-1 A free, open source, portable framework for OpenGL application development (mingw-w64) mingw64/mingw-w64-x86_64-glfw 3.1.1-1 A free, open source, portable framework for OpenGL application development (mingw-w64) # 64bit版の方を入れる $pacman -S mingw-w64-x86_64-glfw # http://www.glfw.org/docs/latest/quick.htmlにあるサンプルコードをコンパイルしてみます。 # サンプルコードをmain.cppに保存 $g++ -I/mingw64/include main.cpp -L /mingw64/lib -lglfw3 -lopengl32 # これでもコンパイルできるが、msys-2.0.dllが必要なのでmingwのgccも入れます。 $pacman -S mingw-w64-x86_64-gcc $x86_64-w64-mingw32-g++ -I/mingw64/include main.cpp -L /mingw64/lib -lglfw3 -lopengl32
相変わらず目的の物はビルドできてないですが。
glewとglfw環境の構築
私の環境はwindows上のmsys2-mingwです。この構成の場合のglfwのlinux向けライブラリの作成方法は分かりませんでした。
存在しないコマンドは適宜pacman -S
で入れてください。
失敗例(msys2-mingw)
まず、glewを導入します。
$git clone https://github.com/nigels-com/glew.git glew $cd glew # system環境変数に合わせたMakefileを実行する。mingwを選択 $ls config config.guess Makefile.darwin-universal Makefile.haiku Makefile.linux-mingw32 Makefile.nacl-64 version Makefile.cygming Makefile.darwin-x86_64 Makefile.irix Makefile.linux-mingw64 Makefile.netbsd Makefile.cygwin Makefile.fedora-mingw32 Makefile.kfreebsd Makefile.linux-mingw-w64 Makefile.openbsd Makefile.darwin Makefile.freebsd Makefile.linux Makefile.mingw Makefile.solaris Makefile.darwin-ppc Makefile.gnu Makefile.linux-clang Makefile.nacl-32 Makefile.solaris-gcc # コンパイル $env SYSTEM=mingw make # 確認 $ls lib glew32.dll glew32mx.dll libglew32.a libglew32.dll.a libglew32mx.a libglew32mx.dll.a
コンパイルできたので、リンクできるようにします。
$pwd /d/work/glew-1.13.0 # パッケージ作ったり何らかの方法でバージョン管理した方が良いような気がしましたが、やり方が分かりませんでした $cp -r include/GL /usr/include $cp lib/* /usr/lib
次はglfwを導入します。
$git clone https://github.com/glfw/glfw $cd glfw $cmake . -- Building for: Visual Studio 14 2015 # VS入れてたせいかもしれませんが、勝手にVS向けのプロジェクトを吐いてます。 # 何かの環境変数を読んでいるみたいですね。 # このままだと、前回作成したプロジェクトのキャッシュが残っているので消します $rm CMakeCache.txt # 指定可能なターゲット環境はcmake -hで確認できます $cmake -h MSYS Makefiles = Generates MSYS makefiles. #-Gオプションでターゲット環境指定 $cmake -G "MinGW Makefiles" $cmake -G "MSYS Makefiles" # 以下のエラーの解決方法が分からず諦め CMake Error: The following variables are used in this project, but they are set to NOTFOUND. Please set them or make sure they are set and tested correctly in the CMake files: RT_LIBRARY (ADVANCED) linked by target "particles" in directory D:/git/glfw/examples linked by target "threads" in directory D:/git/glfw/tests linked by target "empty" in directory D:/git/glfw/tests
ビルドは上手くいった(virtual box上のubuntu)
# glewのコンパイル $sudo apt-get install glew-utils $git clone https://github.com/nigels-com/glew.git glew $cd glew $sudo cp -r include/GL /usr/include $sudo cp lib/*.a /usr/lib # glfwのコンパイル $git clone https://github.com/glfw/glfw # コンパイルに必要なライブラリを追加 $sudo apt-get build-dep glfw # 足りなくてエラーが出た分を追加 $sudo apt-get install libxinerama-dev $sudo apt-get install libxcursor-dev $cd glfw $cmake . $make # リンク $sudo cp -r include/GLFW /usr/include $sudo apt-get install libglfw3 $sudo apt-get install libglfw3-dev
ここまでやって、結局目的のコードをコンパイルできませんでした。無念。
std::vectorの正しい使い方
イテレータ無効化ルール
イテレータは容易に無効化されます。
Iterator Invalidation Rules (C++0x)
例えばvectorはコンテナサイズよりも要素が増えた場合、要素が取り除かれた場合にイテレータは無効化されます。
なので、基本的にイテレータが無効化される操作とされない操作を明確に分離して考える事で危険を減らすと良いと思います。
const性とイテレータ無効化ルール
const vectorは一切の変更を認めませんが、イテレータ無効化ルールは要素の増減が無ければ変更を許します。
const vectorは変更をコンパイルエラーにしてくれますが、イテレータ無効化ルールは要素の増減に対してエラーを出してくれません。
そこで、イテレータ無効化を避ける方法としてSTLのalgorithmを使います。
algorithmは明示的に破壊しなければイテレーション中にイテレータは無効化されない為です。
イテレーション
基本的にできる限りイテレータを触れない様にすべきです。
何故ならイテレータへの操作はうっかりした破壊が発生しやすい上に、そのミスが分かりにくいからです。
基本的にrange based forが見た目も優れているので使うべきなのですが、イテレーション中のコンテナに触れてしまう問題があるので、心配な場合はfor_eachでも良いかもしれません
std::vector<int> v; std::for_each(v.cbegin(), v.cend(), [](int number){ // ここだと明示的にしなければvに触れない }); // 明示的にイテレータを破壊する std::for_each(v.cbegin(), v.cend(), [&v](int number){ // 正しくイテレートできなくなる v.push_back(number); });
イテレーション中にコンテナを触れない認識が共有されている場合は、値を書き換えする意図があるのかを明示します。
// 値は参照のみと明示 for(const auto &item: v){ // itemは参照のみ可 } // 変更を行うと明示 for(auto &&item: v){ // moveした場合コピーコストが減るので&&で受ける }
もし、イテレータを見えるようにしつつもread onlyにしたい場合は
cbegin
やcend
を使います。こっちをデフォルトで使っていく気持ちが大事です。
要素の追加
要素の追加はreserveしていなかった場合にイテレータが壊れる可能性があります。
なので、基本的に追加した場合にイテレータが壊れると考えた方が安全です。
// イテレータが壊され正常に表示されない std::vector<int> v = {1, 2, 3, 4}; for(const auto &item: v){ std::cout << item << std::endl; v.push_back(item); }
また要素の追加の際のコンストラクトコストを考えるとemplace_backを使うべきです。
std::vector<Foo> foos; // Foo構築後Fooのコピーコンストラクタによって渡されFooは破棄される foos.push_back(Foo()); // 直接構築するので無駄なオブジェクト生成が行われない foos.emplace_back();
また、追加の際にメモリの再確保が行われると大量のコピーが発生します。
クラスをSTLコンテナにいれると恐ろしい事が起こるぞ! - 神様なんて信じない僕らのために
reserveとemplaceが意識共有されていない間はshared_ptrやunique_ptrを使うと良いと思います。
コピーについて認識が薄い内はunique_ptrがmoveしないとコピーできなくて不思議に思うかもしれませんが、オーナーシップを知る良い機会だと思います。
要素の削除
// 最悪。いくらでもバグが入り込む余地があり、なおかつ分かりにくい std::vector<int> v(10); for(int i = 0; i < 10; ++i){ if(v[i] == 5){ // 範囲外アクセスしてても気付きにくい for(int j = i; j < 10; ++j){ v[j] = v[j + 1]; } } }
// イテレータへの深い理解が必要なので積極的に使うべきではない std::vector<int> v(10); for(auto it = v.begin(); it != v.end();){ if(*it == 5){ // イテレーション中にvにアクセスする事は危険 it = v.erase(it); // eraseの戻り値をインクリメントせずにチェックする必要があるが忘れそう continue; } ++it; }
以下がオススメの記述です。
イテレータの無効化範囲が明確で間違いが少なくなります。
// 明示的に破壊しなければイテレータは壊れない auto remove_begin = std::remove_if(v.begin(), v.end(), [](int number){ return number == 5; }); // コンテナの要素数を変更するメソッドはイテレータを無効化する v.erase(remove_begin, v.end()); // これ以降はイテレータは無効化されている
サイズ管理
何度も説明に出て来た様に想定限界数を設定し、reserve
する事が大事です。
しかし、データを全部読み込んでそれ以降サイズを変更しないという場合にはメモリがもったいないです。そんな時はshrink_to_fit
を使いましょう。
std::vector<int> v; // reserveは必要 v.reserve(100); v.emplace_back(); v.emplace_back(); v.emplace_back(); // vの確保メモリがint3つ分になる v.shrink_to_fit();
戻り値としてのstd::vector
NRVOとかmove semanticsがあるので、現代のまともなコンパイラでは参照やポインタを経由せずに返してもコストがありません。
// 戻り値を参照やポインタにしなくても良い std::vector<int> DefaultList(){ std::vector<int> v; v.reserve(2); v.emplace_back(1); v.emplace_back(2); return v; }
privateコンストラクタじゃなくてDefaulted and Deleted Functionsを使おう
概要
参考
Explicitly Defaulted and Deleted Functions
privateコンストラクタによるコピー不可クラスにはいくつかの問題があります。
コピーコンストラクタをprivateにするとコンストラクタが必要になる
struct NonCopyable { // 何もしない場合でもコンストラクタを明示的に宣言する必要がある。 NonCopyable() {}; private: NonCopyable(const NonCopyable&); NonCopyable& operator=(const NonCopyable&); };
空のコンストラクタとデフォルトコンストラクタでは挙動が違う
詳しくは過去記事を参照してください。
C++の初期化は分かりにくい - ぷろみん
簡単に説明すると、空のコンストラクタはメンバの値が不定値になりやすいという事です。
メンバ関数やfriend関数はprivateコピーコンストラクタにアクセスできてしまう
参照した場合宣言のみの関数なのでエラーとなってしまいます。
意図が明確では無い
privateのコピーコンストラクタの挙動を把握している人の間でしか理解できない記述はよろしくないです。
C++11のDefaulted and Deleted Functionsを使おう
struct NonCopyable { NonCopyable() = default; NonCopyable(const NonCopyable&) = delete; NonCopyable& operator=(const NonCopyable&) = delete; };
git for windowsからmsys gitに移行しました。
モチベーション
最近サーバを触り始めたので、ConEmuを導入してみました。
フォントや使い心地に概ね満足していたのですが、ローカルでvimを起動すると^M
が付くエラーが大量に出てしまいました。
NeoBundle関係が大体使えなくなってしまっていました。
KaoriYa版Vimとgit for windows
のセットで使っていた物をそのままmsys環境に持っていってしまったのが良くなかった様子です。
^M
環境によって改行の扱いが違うのですが、改行コードの扱い方を意識していませんでした。
NeoBundleの使い方か、gitの改行を付ける仕組みか、使っているshellも問題なのか、何が原因かは分かりませんでした。
とりあえず解決策
msys git
のsh.exe
を使うmsys git
のvim.exe
を使うmsys git
のgit.exe
を使う