ぷろみん

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

gitの最新版をビルドする

環境

https://atlas.hashicorp.com/ARTACK/boxes/debian-jessieで作成されたdebianのboxを使っています。

$ sudo aptitude update
$ sudo aptitude -y install git
$ sudo aptitude -y install libcurl4-gnutls-dev libexpat1-dev gettext libz-dev libssl-dev
$ git clone https://github.com/git/git

$ cd git
$ git tag | tail
v2.5.0-rc0
v2.5.0-rc1
v2.5.0-rc2
v2.5.0-rc3
v2.5.1
v2.5.2
v2.5.3
v2.6.0-rc0
v2.6.0-rc1
v2.6.0-rc2
# 2.5.3をインストールすることにする
$ git checkout refs/tags/v2.5.3

# 後はビルドするだけなのでバージョンの古いgitをアンインストールする
$ sudo aptitude -y purge git

$ make prefix=/usr/local all
$ sudo make prefix=/usr/local install

# 環境変数を適用する為にシェルを再起動
$ exec -l $SHELL
$ git --version
git version 2.5.3

Git - Gitのインストールを参考にインストールしましたが、v2は何が違うのでしょうか?

debianでglfwをコンパイルしてwindows向けバイナリを作成する

概要

windows環境での話です。
最近msys-gitやmysy2を使ったりで環境変数やら実行バイナリがごちゃごちゃしてきてしまったので、実験用の環境が欲しくなったので仮想化してみます。

事前準備

管理者権限が必要なパッケージもあります。

  • chocolatly
    https://chocolatey.org/
    サイトに記載されているコマンドを打てばインストールできます。

  • virtual box

$choco install virtualbox
$choco install vagrant
$choco install virtualbox.extensionpack

もしかしたらOpenSSHも必要かもしれません。

choco install openssh

sshはパスが自動で付かないかもしれません。

Windows 8.1ではじめるイマドキの開発環境 - Qiita

vagrant

http://www.vagrantbox.es/から好きなboxを引っ張ってきます。 とりあえずDebianでいきます。

#powershell か msys-mingw
$mkdir debian
$cd debian
$vagrant box add https://atlas.hashicorp.com/ARTACK/boxes/debian-jessie
$vagrant box list
ARTACK/debian-jessie (virtualbox, 8.1.0)
$vagrant init ARTACK/debian-jessie
$vagrant up
$vagrant ssh

Oracle VM VirtualBox Extension Packを入れていないとvagrant upの際に以下のエラーがでます。

The guest machine entered an invalid state while waiting for it
to boot. Valid states are 'starting, running'. The machine is in the
'poweroff' state. Please verify everything is configured
properly and try again.

If the provider you're using has a GUI that comes with it,
it is often helpful to open that and watch the machine, since the
GUI often has more helpful error messages than Vagrant can retrieve.
For example, if you're using VirtualBox, run `vagrant up` while the
VirtualBox GUI is open.

The primary issue for this error is that the provider you're using
is not properly configured. This is very rarely a Vagrant issue.

言われた通りGUIで起動してみます。
するとImplementation of the USB 2.0 controller not found!というエラーが出ます。

ビルド

ここからはvagrantで起動したdebian上での実行例です。

# 必要なパッケージを揃える
$sudo aptitude update
$sudo aptitude -V -D -y install git
$sudo aptitude -V -D -y install cmake
$sudo aptitude -V -D -y install mingw-w64

# mingw版のglfwをビルド
$git clone https://github.com/glfw/glfw
$cd glfw
$cmake -DCMAKE_TOOLCHAIN_FILE=CMake/x86_64-w64-mingw32.cmake .
$make
$sudo make install

# http://www.glfw.org/docs/latest/quick.htmlにあるサンプルをhello.cppとして保存

$x86_64-w64-mingw32-g++ -I/usr/local/include hello.cpp -L/usr/local/lib -lglfw3 -lopengl32 -lwinmm -lgdi32
# windowsとの共有フォルダに実行ファイルを送る
$mv a.exe /vagrant/a.exe

windows側からa.exeを実行すると無事に実行できました。

後片付け

遊び終わったら環境を片付けましょう。

# debian
$exit

# windows
$vagrant halt
$vagrant destroy --force

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;
}