ラベル プログラミング の投稿を表示しています。 すべての投稿を表示
ラベル プログラミング の投稿を表示しています。 すべての投稿を表示

2014年9月6日土曜日

プログラミングの理論 計算機プログラムの構造と解釈

プログラミングの勉強というと、どういうことを思い浮かべますでしょうか。
実践的には言語の仕様やSTLを学ぶというのは大切でしょう。情報科学としてはアルゴリズムとデータ構造、あるいは情報数学のようなものになるでしょうか。

もちろん言語・STLの仕様とは科学者工学者の英知の結晶でありますから、吸収できるものはたくさんありますし、アルゴリズム等を知ることは実装の幅を広げてくれるでしょう。しかしそのどちらもプログラミングの理論かといわれると、少し遠いように思えます。

Just let me code.
プログラミングは理論というより手を動かすものじゃないかと思っていたのですが、そんなときに「計算機プログラムの構造と解釈」(Structure and Interpretation of Programming)という本に出会いました。これはまさしく「プログラミングの理論」といえる内容の本です。

プログラムを実装するに当たってどう考えればいいのか、何を考慮しなければならないのか、良いコードとは何か。言語仕様等に依存せずに、抽象化、構造化、関数型プログラミング、オブジェクト指向、論理プログラミングといった考え方を学ぶことが出来ます。詳しい内容はAmazonのレビューに任せ、何故この本が良いのかに絞ってお話をします。

こういう洋書のいい所は、言葉だけで説明するのではなくちゃんとコードでも説明して、なおかつ練習問題を用意してくれることに思えます。(練習のためのコードはschemeですが、その文法を学ぶことを通じてソフトウェア工学を学べるというつくりになっています。)学術書一般において和書に比べて洋書が厚い・高いのはこういう寄り添った説明があるからでしょう。

この本はMITの一年生の1学期の教科書として書かれ、今は多くの大学でも初年時のプログラミングの教科書となっています。初年時の教科書ですからプログラミングを始める人も読めますし、プログラミング経験が豊富な方もこの本で理論を学んではいかがでしょうか。

ひたすらにコードを書くだけでは学べないものが学べます。特にプログラマを目指す人は必ず読むべきだと思います。


2014年6月21日土曜日

C++11 スマートポインタの使い方・用途・サンプル

C++はガーベジコレクションがないので、動的にallocateしたオブジェクトは明示的にdeallocateしなければなりません。これを怠るとメモリリークなどもの問題が出てくるわけです。また逆に、オブジェクトを破棄したポインタを使いつづけるとnullポインタ、或いはダングリングポインタとしてエラーの原因になります。

C++において生来的ともいえるこれらの問題を解決しようというのがスマートポインタの機能です。簡単に言うと、ポインタのスコープに合わせてオブジェクトが勝手にdeallocate(deconstruct)されます。これによってオブジェクトとポインタの寿命が一致するのでメモリリーク、ダングリングポインタが防げます。

スマートポインタには何種類かあり、それぞれ異なる仕様・用途があります。以下、unique_ptr, shared_ptr, weak_ptr, auto_ptr(C++11以前)の説明をします。


1. unique_ptr

一番使われるのはunique_ptrでしょう。以上に述べたスマートポインタの特徴に加え、名の通り「指しているオブジェクトに対する唯一のポインタ」であることを保証します(低レベルで色々やれば崩せますが)。即ち、unique_ptrは他のスマートポインタからオブジェクトをコピーしたりアサインしたりすることが出来ません。

オブジェクトを他のポインタに割り当てたい場合はstd::moveを使う必要があります。これを使うと元のポインタはnullポインタになるので、やはり一つのポインタしかそれを指すことが出来ません。

以下のコードのように、いちいちdeleteなどしなくても使うことができます。


void unique_ptr_test() {
    cout << "----- unique_ptr test -----" << endl;
    std::unique_ptr<int> first(new int(1));
    std::unique_ptr<int> second(new int(2));

    // 関連して、nullptrも新しく加わった。0, NULLと区別される。
    std::unique_ptr<int> nullpo(nullptr);

    cout << "*first = " << *first << endl;
    cout << "*second = " << *second << endl;

//    std::unique_ptr<int> second = first; ERROR: コピーは出来ない。
//    second = first; ERROR: アサインも出来ない。

    // moveを用いることでオブジェクトをsecondに移すことが出来る。
    // そのとき、firstはnullptrになるので注意。
    // つまり、unique_ptrを参照できるポインタは常に一つである。(定義より)
    second = std::move(first);
    cout << endl << "second = std::move(first);" << endl << endl;
    cout << "*fisrt = " << (first? "not null" : "null") << endl;
    cout << "*second = " << *second << endl << endl;

    cout << "nullpo is " << (nullpo? "not null" : "null") << endl;

    // unique_ptrはスコープの外に出るときにdeconstructorを呼ぶ。
    {
        std::unique_ptr<Foo> locale(new Foo());
    }
}


2. shared_ptr

unique_ptrの使いやすさの一つに「オブジェクトを指す唯一のポインタ」であることがあるでしょうが、逆にそうでない方が使いやすい場合もあるでしょう。複数のスマートポインタで同じオブジェクトを指したい時に使われるのがshared_ptrです。

shared_ptrはunique_ptrと異なり、コピー・アサインをすることができます。また、オブジェクトを指している複数のshared_ptrのうちの一つだけを初期化したい場合にreset()を呼ぶことで、そのオブジェクトを破棄せずに(それによって他のポインタがnullポインタにならずに)そのポインタだけをnullポインタにすることができます。

また、共有されているオブジェクトはshared_ptrの全てがスコープから外れた時に破棄されます。



void shared_ptr_test() {
    cout << "----- shared_ptr test -----" << endl;
    std::shared_ptr<Foo> first(new Foo());
    std::shared_ptr<Foo> copy = first;

    // copyがまだあるのでresetでもオブジェクトは持続される。
    // しかしfirstはnullポインタになる。
    first.reset();
    cout << "first = " << (first? "not null" : "null") << endl;
    cout << "copy = " << (first? "not null" : "null") << endl;

    // copyもreset()が呼ばれるとdestructor
    copy.reset();
}


3. weak_ptr

 weak_ptrはshared_ptrをサポートするスマートポインタです。
weak_ptrはshared_ptrが指しているオブジェクトを同様に指すことが出来ます。しかし、先ほどshared_ptrの指すオブジェクトは、「shared_ptrの全てがスコープから外れたら破棄される」とありましたが、つまりweak_ptrが指していても、shared_ptrが全てなくなれば破棄される、ということです。

ではweak_ptrは何に使われるのかというと、 shared_ptrを用いてオブジェクトを破棄して新しいオブジェクトをconstructしたり、という流れの中で使われます。つまり、weak_ptrでshared_ptrのオブジェクトを参照しておき、それからshared_ptrで新しいオブジェクトをallocateすると、weak_ptrはNullを指すことになります。使ってみるとこれが便利な場合が結構あります。



void weak_ptr_test() {
    cout << "----- weak_ptr test -----" << endl;


    // 通常のポインタはDangling pointerとなるリスクがある。
    // 例えばここでrefはdangling pointerである。
    int* ptr = new int(10);
    int* ref = ptr;
    delete ptr;

    // それに対して、
    // weak_ptrは対象のオブジェクトがdeallocateされたかをexpired(), lock()で確認することが出来る。

    std::shared_ptr<int> sptr;
    // takes ownership of pointer
    sptr.reset(new int(10));

    // weak1は*sptrを参照はするが、保持はしない。
    std::weak_ptr<int> weak1 = sptr;

    // 前のオブジェクトを破棄し、新しいオブジェクトを作る。
    // ここでweak1がshared_ptrなら一緒に5を指すことになるが、ここではweak_ptrなので参照オブジェクトがなくなる。
    sptr.reset(new int(5));

    // 5を指すweak_ptr
    std::weak_ptr<int> weak2 = sptr;


    // weak1の指していたオブジェクトがdeallocateされたかはlock()で確認することが出来る。
    if ( auto tmp = weak1.lock() ) {
        std::cout << "weak1.lock() = " << *tmp << '\n';
    } else {
        std::cout << "weak1 is expired\n";
    }

    if ( auto tmp = weak2.lock() ) {
        std::cout << "weak2.lock() = " << *tmp << '\n';
    } else {
        std::cout << "weak2 is expired\n";
    }

}


4. auto_ptr (C++11以前)

 auto_ptrはレガシーです。簡単に言うならunique_ptrの下位互換です。C++11以前からあるので(これを発展させたものが今まで説明してきたスマートポインタら)、C++11を使いたくない事情がある場合に代用として使えます。基本的に使い方はunique_ptrと同じです。ただ隠蔽構造がやや不完全で、例えば以下のコードだとy = xでunique_ptrのy = std::move(x)に当たり、ここでxはnullポインタになるので、なかなか注意が必要な構造をしています。



void auto_ptr_test() {
    cout << "----- auto_ptr test -----" << endl;
    auto_ptr<int> x(new int(5));
    auto_ptr<int> y;

    y = x;

    cout << "x is " << (x.get() ? "not null" : "null") << endl; // Print NULL
    cout << "y is " << (y.get() ? "not null" : "null") << endl; // Print non-NULL address i
}


スマートポインタは通常のポインタと比べてそんなに遅くないそうです(アセンブリにして追加一行程度とのこと)。特にパフォーマンスが求められていない部分のコードは手軽く使っていけるようです。

2014年5月2日金曜日

printfよりもデバッガを使おう

デバッガを使わない人って意外にいるように思えます。

printfとかの出力で済ませられるバグも多いでしょう。いちいちデバッガをつかうのも大袈裟です。私がデバッガを使うべきだとする目安は、「ここが原因だろう」と考えて書いたprintfがハズレだった場合です。

多くの場合原因の候補箇所はいくつかの、直近に編集した表層部分のエラーでしょう。九割くらいのバグはここで解決されます。

しかし予想が外れた場合。これはえらいことです。エラーの原因がシステムのより深いところにあったということになります。人は、こういう予想外の自体に対してより近視眼にして対処してしまいがちだと言います。結果としてだんだんコードの狭い部分、狭い部分へと目を絞ってしまいます。

printfでのデバッグは、「ここに原因があるだろう」と予想して、それが正しいか否かしかわかりません。対してデバッガは「どの辺りに原因があるか」を測ることが出来ます。

謂わば、printfがクローズドクエスチョンなのに対してデバッガはオープンクエスチョンが出来る、ということです。

なので原因がよく分からないバグに対してはデバッガを使う方が賢明に思えます。二分木探索の方がリストを頭から探索するより速いですし。(そういう理由でデバッガの使い方のコツとして、二分木探索を心懸ける、というのがあったりします。)

デバッガの使い方は、UI部分はネットにいっぱい資料があります。「Eclipse デバッガ」で検索すれば丁寧な解説が見られるでしょう。デバッグの理念、例えば上述の二分木探索みたいな話を知りたい方は以下の「実践 デバッグ技法」をお勧めします。