ぷろみん

プログラミング的な内容を扱ってます

windows.hが必要ないwindows GUIアプリケーションの作り方

概要

int WINAPI WinMainというエントリポイントにも関わらずヘッダが必要な関数があります。
コンソールアプリケーションでプロジェクトを作るとint main()から始める事ができるが、アプリケーションの動作中ずっと不要なコンソールが出現したままになります。

解決策

まず、Windowsアプリケーションでプロジェクトを作成します。
次にConfiguration Properties -> Linker -> Advanced -> Entry PointmainCRTStartupと書き込みます。

これで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;
}

参考

N3701 concept lite

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

相変わらず目的の物はビルドできてないですが。

f:id:torini:20150828211533p:plain

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にしたい場合は cbegincendを使います。こっちをデフォルトで使っていく気持ちが大事です。

要素の追加

要素の追加は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版Vimgit for windowsのセットで使っていた物をそのままmsys環境に持っていってしまったのが良くなかった様子です。

^M

環境によって改行の扱いが違うのですが、改行コードの扱い方を意識していませんでした。
NeoBundleの使い方か、gitの改行を付ける仕組みか、使っているshellも問題なのか、何が原因かは分かりませんでした。

とりあえず解決策

  • msys gitsh.exeを使う
  • msys gitvim.exeを使う
  • msys gitgit.exeを使う