ぷろみん

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

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を使う

Visual Studio Community 2015のデフォルトテーマ変更

VS Community 2015をインストールしたら、また白くなっていたので黒に戻そうとしましたが、どこから設定するか調べるのに思ったより手こずったのでメモしときます。

Tools -> Options -> Environment -> General -> Color Theme

一番上にあるのに気付かなかった!

Fonts and Colorsとかに気をとられてしまっていました。

静的optional

モチベーション

ヘッダの依存性を減らしたい(コンパイラファイアウォールを強くしたい)
しかし、動的メモリ確保を行いたくない

おしいoptional

optionalは静的確保できるポインタの様な物です。
静的型をnullableにする為に使われます。

後でコンストラクトできるという特性から不完全型で構築できるかなと期待しますが、できません。
なぜなら静的なメモリを最初に用意する必要がある為です。
つまり、型のサイズさえ分かれば不完全型での宣言が可能になります。

実装

// optional.h
#pragma once
#include <cstring>

namespace torini{

// optionalの簡易イメージ
template<class T, int N>
class Optional{
public:
    void Construct(){
        T t;
        std::memcpy(this, &t, N);
    }
    
    T* operator ->(){
        return reinterpret_cast<T*>(this);
    }
private:
    // 通常はこれに加えて構築済みかどうかのフラグが存在する
    char storage[N];
};

}
// foo.h
#pragma once
#include <string>

namespace torini{

// 前方宣言用。なんでも良い
class Foo{
public:
    void Print() const;

private:
    std::string message_ = "hello world";
};

}
// foo.cpp
#include "foo.h"
#include <iostream>

namespace torini{

void Foo::Print() const{
        std::cout << message_ << std::endl;
}

}
// foo_size.h
#pragma once

namespace torini{

int GetFooSize();

}
// foo_size.cpp
#include "foo_size.h"
#include "foo.h"

namespace torini{

int GetFooSize(){
    // Fooのサイズを知る為にはfoo.hが必要
    return sizeof(Foo);
}

}
// main.cpp
#include "optional.h"
#include "foo_size.h"

namespace torini{

// torini::Fooを前方宣言だけする
class Foo;

}

int main(){
    // 前方宣言とサイズを取得する事により静的ストレージを確保しつつ好きなタイミングでコンストラクトできる
    torini::Optional<torini::Foo, torini::GetFooSize()> foo;

    // foo.hをincludeしないとコンストラクトはできない
    //foo.Construct();
    //foo->Print();
}

感想

面倒なので計測して問題時間が支配的になるまでは適当で良いと思います。

サイズ取得の為の関数がジェネリックじゃないのも良くないですし。