C++チップス

最近 120 年ぶりに C++ を書くことにしたので、もだーんとは何かという禅問答を繰り返した記録を公開。

C++ のススメ

なぜ C ではなく C++ を使うのか。その理由の 99.999% は std::string にある。
char* は忘れるんだ! std::string を使うだけで↓のように幸せになれる!

 
void f(const std::string& s) {
g("hoge "+s);
}

C ならこんな感じになるところだ。

void f(const char* s) {
char buf[256];
sprintf(buf, "hoge %s", s);
g(buf);
}

ライブラリ

STL は機能リッチとはとても言えないので、ちょっとなにかやりたいことがあるとライブラリをまず探すという面倒なことになる。まあ主要なところをいくつかまとめておく。

用途名称URLコメント
広範 Boost http://www.boost.org/ 事実上標準的な地位にある。機能はとても豊富。でもテンプレートメタプログラミングを駆使しているのでコンパイルがとても遅い。私は好きじゃない。
広範 POCO http://pocoproject.org/ MySQL ライブラリ等を備えていて boost よりも実用的アプリに向く。ネットワークアプリを作るのにとても便利。激しくお勧め。
マルチスレッド TinyThread++ http://tinythread.sourceforge.net/ ミニマリスト。生スレッドの 100 倍はマシ。
JSON picojson https://github.com/kazuho/picojson @kazuho 作。お勧め。

Tips

  • 初期化子
    メンバー変数の定義順(出現順)に初期化子も並べる必要がある。正確には、初期化子がどういう順序で書かれていようとも定義順に初期化がなされるので、初期化子の順番に初期化がされると思うととんでもない不具合になる。コンパイラで -Wall とかしておけば警告してくれる。
  • クラスメンバの初期化
    primitive はスタックオブジェクトだと 0 初期化されない。ので、必ず初期化子で初期化する。オブジェクトはデフォルトコンストラクタ呼んでくれるので初期化子で明示的に初期化する必要は普通はない。
  • Javaのようにオーバーロードしたコンストラクタを参照したい
    できません。コンストラクタは初期化だけすればよろしい。以上。
  • finally ないの?
    この Java 脳め!
    C++ はスタックオブジェクトはスコープを外れるとデストラクタを必ず呼んでくれるので、それを使えばリソースを確実に解放できる。auto_ptr などはそれを利用している。RAIIという。
    void f() {
    std::auto_ptr<T> t(new T(...));
    ... do anything ...
    // no need for delete t
    }
  • RAII
    Q. RAII はいいんだが、配列とかどう RAII するの?
    A. auto_ptr は配列には使えないので、vector でやる。
    std::vector<char> v(SIZE);
    char* buf = &(v[0]);
  • 例外安全
    new するな。以下のように auto_ptr と vector を使う。
    class C {
    C(...): m_object(new T(...)), m_array(SIZE) {}
    std::auto_ptr<T> m_object;
    std::vector<T> m_array;
    };
  • 例外
    stdexcept に基本的なのはある。
    なげるときはスタックオブジェクトでいい。処理系が例外オブジェクトにコピーしてくれる。
    なので、コピー可能なオブジェクトである必要がある。catch するときはそのコピーを参照で受け取る。
    #include <stdexcept>
     
    class MyException: public std::runtime_error { ... };
    try {
    throw MyException("error");
    } catch( const MyException& e ) {
    ...
    }
  • 例外は投げ捨てる漢へ
    バックトレース出力を追加すると幸せ。
    std::terminate_handler orig_handler = 0;
    const int MAX_BACKTRACE = 100;
    void my_terminate() {
    std::cerr << "Backtrace:" << std::endl;
    void* bt[MAX_BACKTRACE];
    int bt_num = backtrace(bt, MAX_BACKTRACE);
    backtrace_symbols_fd(bt, bt_num, 2);
    std::cerr << std::endl;
    if( orig_handler ) orig_handler();
    abort();
    }
     
    int main(int argc, char** argv) {
    orig_handler = std::set_terminate(my_terminate);
    throw std::runtime_error("Failed to open "+argv[0]);
    return 0;
    }

    実行例:

    Backtrace:
    ./tbs[0x402b4f]
    /usr/lib/libstdc++.so.6(+0xcad16)[0x7f040877cd16]
    /usr/lib/libstdc++.so.6(+0xcad43)[0x7f040877cd43]
    /usr/lib/libstdc++.so.6(+0xcae3e)[0x7f040877ce3e]
    ./tbs[0x40305e]
    ./tbs[0x402c35]
    /lib/libc.so.6(__libc_start_main+0xfd)[0x7f0407eb3c4d]
    ./tbs[0x401c09]
    
    terminate called after throwing an instance of 'std::runtime_error'
      what():  Failed to open /home/ymmt/eieie
    Abort
    
  • ファイルローカルスコープ
    static は時代遅れ。無名名前空間を使え。
    namespace {
    int local = 3;
    }
  • const ってヘッダに書けるの?
    const 変数は既定でコンパイル単位ローカル。なので書ける。
  • デフォルトコンストラクタのオブジェクトを作る
    以下のコードはダメ。関数になってしまうので。
    Object o();

    正しくはこう。

    Object o;