p_tan's blog

勉強日記です。ツッコミ大歓迎

Windows XPでVundle使おうとしたらハマった

vimのプラグイン管理用プラグイン(ややこしい)であるVundleをWindows XPで使おうとしたらハマったので、解決した手順をメモっておく。

  • 問題
    • VundleでいざBundleInstallしようとしたら、error: inflate: data stream error (incorrect header check) とか言われてgit cloneが失敗
  • 原因
    • cygwin で git するときは binary mode で mount しないといけないらしい
  • 解決方法
    • 参考 : ヨタの日々
    • /etc/fstab 内の 「none /cygdrive cygdrive text,posix=0,user 0 0」みたいな行で mount の指定をしてるので、text -> binary とか修正すると良い

const参照引数でハマった

const参照の引数でうっかりしてハマったこと。

以下のような、レンジの全ての要素から、一括してある値を引くようなヘルパ関数を作っていた。
(実際は足し算や掛け算などもサポートするように汎用的に書いていたが)

template<class T, class Range>
void Sub(Range &r, const T& value){
    for(auto& elem : r){
        elem -= value;
    }
}

int main(){
    int data[] = {1, 2, 3};
    Sub(data, 1);  // -> data = {0, 1, 2} 便利!
}

さて、配列の最小値が0になるように値をシフトさせようと思って、ヘルパ関数を以下のように使用したら意図した結果にならなかった。

#include <algorithm>
#include <iterator>

int main(){
    int data[] = {1, 2, 3};
    Sub(data, *std::min_element(std::begin(data), std::end(data)));  // -> data = {0, 1, 2} ではなく, {0, 2, 3} になる???
}

問題は、関数Subの引数valueに、第一引数rの要素への参照が渡っているため、const参照であるにも関わらず、関数の実行中にvalueの値が変化したことにある。
参照によるオブジェクトの共有の問題は、頭では理解していても、うっかりでバグを埋め込む可能性が高い。しかも非常に見つけづらいバグなので厄介だ。

データメンバに触らなければヌルポインタの参照外しを行なっても実行時エラーにならない

恥ずかしながら知らなかった。
ヌルポインタの参照外しを行ったらすぐにセグメンテーションフォールト等で落ちるもんだと思い込んでたけど、データメンバに触らなければ実行できてしまうようだ。
規格上認められてるのか、それとも未定義動作なのか。

#include <iostream>
using namespace std;

class X {
    int member;
public:
    X(int m):member(m){}
    // データメンバに触らないメンバ関数
    void func(){
        // member = 1;    // この行のコメントを外すとセグフォ
        cout << "X::func()\n";
    }
};

int main(){
    X *p = nullptr;
    p->func();
    (*p).func();
    return 0;
}

出力

X::func()
X::func()

これに関連して、ポインタと参照を併用する場合に参照がNULLを指す事が起こったりする。

class X {
    int member;
public:
    void set(int val){
        member = val;
    }
};

void func(X&);  // 宣言

int main(){
    X *p = nullptr;
    func(*p);  // ヌルポインタの参照外しで関数funcを呼び出し
        // この時点ではセグフォは起きない
    return 0;
}

void func(X& x){  // 定義
    x.set(1);    // ここでセグフォ
}

X::set()を呼び出してるところがさらに関数の深いネストの奥だったりすると、再現性の無いバグに悩まされるかもしれない。ポインタと参照を併用する場合は気を付けよう。

autoを使おう

GoingNative 2012のスライドを眺めていたらびっくりしたのでメモ。
STL11: Magic && Secrets | GoingNative 2012 | Channel 9のスライド24枚目。

map<string, int> m;
for(const auto& p1 : m)
for(const pair<string, int>& p2 : m)

このfor文、どっちが速い?って問い。

答えは上の方。

下が遅いのは、mのvalue_typeはpair<const string, int>だから一時オブジェクトpairが発生してstringがコピーされるから、だそうで。

とりあえず型を指定したい時以外はautoを使えばいいみたい。

EigenでNMF

C++の行列演算ライブラリEigenを使うとNMFが超簡単に実装できる。

NMF : Non-negative Matrix Factorization

Wikipediaとか、この文献見るといいです。
Daniel D. Lee and H. Sebastian Seung (2001). "Algorithms for Non-negative Matrix Factorization". Advances in Neural Information Processing Systems 13: Proceedings of the 2000 Conference. MIT Press. pp. 556–562.

コード

#include <Eigen/Core>

/*! ***********************************
 @brief V ≒ WH となるようにNMF
 
 || V - WH || を最小化するようにNMFを実行

 @param[in] V   n x m 行列
 @param[in] k   分解する要素数
 @param[out] W  n x k 行列
 @param[out] H  k x m 行列
 @param[in] loop_max W, Hの更新回数
 @pre k > 0
 ***************************************/ 
void NMF(
    const Eigen::MatrixXd &V, unsigned int k,
    Eigen::MatrixXd &W, Eigen::MatrixXd &H, 
    const unsigned int loop_max = 1000)
{
    // W, Hをランダムな非負数で初期化
    W = Eigen::MatrixXd::Random(V.rows(), k).cwiseAbs();
    H = Eigen::MatrixXd::Random(k, V.cols()).cwiseAbs();
    // W, Hを更新
    for(unsigned int i = 0; i < loop_max; ++i){
        W.array() = W.array() * (V * H.transpose()).array() / 
                     (W * H * H.transpose()).array();
        H.array() = H.array() * (W.transpose() * V).array() /
                     (W.transpose() * W * H).array();
    }   
}

これだけ。EigenマジEigen。

constexpr関数とstatic_assertでコンパイル時ユニットテスト

constexprな関数はstatic_assertでコンパイル時にユニットテストできる。

// ユークリッド互除法による最大公約数
constexpr int gcd(int a, int b)
{
    return b == 0 ? a : gcd(b, a % b);
} 

// constexprな関数gcdのユニットテスト
static_assert(gcd(1, 1) == 1, "gcd() test");
static_assert(gcd(4, 2) == 2, "gcd() test");
static_assert(gcd(4, 6) == 2, "gcd() test");
static_assert(gcd(3, 5) == 1, "gcd() test");

static_assertはブロック内でなくても書けるのが便利。しかも、コンパイル時にしかテストが走らないので、constexpr関数の直下にテストコードを書いておけば、ビルドで変更のあったファイルのみコンパイルすることで無駄なテストが走ることも少なくなる。すばらしい。